mirror of
https://github.com/ivuorinen/actions.git
synced 2026-02-16 21:48:24 +00:00
Compare commits
86 Commits
v2025.11
...
renovate/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abd0c020e2 | ||
|
|
291bb2fdc4 | ||
|
|
8fa4dc84f2 | ||
|
|
c40f80e9c5 | ||
|
|
20fb4bc79c | ||
|
|
9277758f30 | ||
|
|
a9605c642f | ||
|
|
6d25c0f8b6 | ||
|
|
6c04d8b197 | ||
|
|
e6c7e60e25 | ||
|
|
01292232b4 | ||
|
|
052b78f9f7 | ||
|
|
f371da218e | ||
|
|
175a9f5356 | ||
|
|
b3299e0670 | ||
|
|
fb37d38f17 | ||
|
|
80621c08b4 | ||
|
|
77429988fd | ||
|
|
f5cedd5870 | ||
|
|
0b0e96a2ed | ||
|
|
3b71d19480 | ||
|
|
51861a9b40 | ||
|
|
f98ae7cd7d | ||
| cc842575b9 | |||
|
|
cbfddb2433 | ||
|
|
5664cdbfbf | ||
|
|
e740f9d893 | ||
| a247b78178 | |||
|
|
56ff9a511c | ||
|
|
81310f9bd7 | ||
|
|
95b8856c3f | ||
|
|
e69ddbc1e2 | ||
|
|
28e81adc2b | ||
|
|
fb25736f7e | ||
| 54886c3fd5 | |||
|
|
fd030b418f | ||
| 96c305c557 | |||
|
|
5b4e9c8e11 | ||
|
|
2d0bff84ad | ||
|
|
98f260793c | ||
|
|
09ae7517d6 | ||
|
|
61ebe619a8 | ||
|
|
a1d55ac125 | ||
|
|
db86bb2f0d | ||
|
|
5e7b2fbc11 | ||
|
|
43126631c2 | ||
|
|
f6ed49a6dd | ||
|
|
23ac5dbca3 | ||
|
|
a8031d3922 | ||
|
|
30149dd950 | ||
|
|
3a3cdcdefe | ||
|
|
7d28006a83 | ||
|
|
4008db6517 | ||
|
|
7aa206a02a | ||
|
|
8481bbb5cd | ||
|
|
4c0068e6e7 | ||
|
|
5cecfe7cbe | ||
|
|
0288a1c8b8 | ||
| 44a11e9773 | |||
|
|
a52399cf74 | ||
|
|
803165db8f | ||
|
|
d69ed9e999 | ||
|
|
8eea6f781b | ||
|
|
4889586a94 | ||
|
|
e02ca4d843 | ||
|
|
13ef0db9ba | ||
|
|
c366e99ee3 | ||
| fbbb487332 | |||
| abe24f8570 | |||
| 9aa16a8164 | |||
| e58465e5d3 | |||
| 9fe05efeec | |||
| 449669120c | |||
|
|
d9098ddead | ||
| f37d940c72 | |||
|
|
eea547998d | ||
|
|
49159fc895 | ||
|
|
89fd0f3627 | ||
|
|
83cf08ff76 | ||
|
|
90ab7c645c | ||
|
|
d05e898ea9 | ||
|
|
650ebb87b8 | ||
|
|
13316bd827 | ||
|
|
350fd30043 | ||
|
|
587853a9cd | ||
|
|
6cde6d088d |
@@ -17,12 +17,12 @@ runs:
|
|||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version-file: pyproject.toml
|
python-version-file: pyproject.toml
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ runs:
|
|||||||
run: uv sync --frozen
|
run: uv sync --frozen
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: '24'
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|||||||
16
.github/codeql/codeql-config.yml
vendored
16
.github/codeql/codeql-config.yml
vendored
@@ -15,3 +15,19 @@ paths-ignore:
|
|||||||
# Use security and quality query suite
|
# Use security and quality query suite
|
||||||
queries:
|
queries:
|
||||||
- uses: security-and-quality
|
- uses: security-and-quality
|
||||||
|
|
||||||
|
# Suppress specific false positives
|
||||||
|
# These findings have been manually reviewed and determined to be false positives
|
||||||
|
# with appropriate security controls in place
|
||||||
|
query-filters:
|
||||||
|
# docker-publish: Code injection in validated context
|
||||||
|
# False positive: User input is validated and sanitized before use
|
||||||
|
# - Only relative paths and trusted git URLs are allowed
|
||||||
|
# - Absolute paths and arbitrary URLs are rejected
|
||||||
|
# - Path traversal attempts are blocked
|
||||||
|
# - Custom contexts require explicit opt-in via use-custom-context: true
|
||||||
|
# - Wraps docker/build-push-action (trusted Docker-maintained action)
|
||||||
|
# - Action is designed for trusted workflows only (documented in action.yml)
|
||||||
|
- exclude:
|
||||||
|
id: js/actions/code-injection
|
||||||
|
kind: problem
|
||||||
|
|||||||
1
.github/tag-changelog-config.js
vendored
1
.github/tag-changelog-config.js
vendored
@@ -1,6 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
types: [
|
types: [
|
||||||
{ types: ['feat', 'feature', 'Feat'], label: '🎉 New Features' },
|
{ types: ['feat', 'feature', 'Feat'], label: '🎉 New Features' },
|
||||||
|
{ types: ['security'], label: '🔐 Security' },
|
||||||
{ types: ['fix', 'bugfix', 'Fix'], label: '🐛 Bugfixes' },
|
{ types: ['fix', 'bugfix', 'Fix'], label: '🐛 Bugfixes' },
|
||||||
{ types: ['improvements', 'enhancement'], label: '🔨 Improvements' },
|
{ types: ['improvements', 'enhancement'], label: '🔨 Improvements' },
|
||||||
{ types: ['perf'], label: '🏎️ Performance Improvements' },
|
{ types: ['perf'], label: '🏎️ Performance Improvements' },
|
||||||
|
|||||||
208
.github/workflows/action-security.yml
vendored
208
.github/workflows/action-security.yml
vendored
@@ -39,212 +39,30 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Check Required Configurations
|
- name: Run Security Scan
|
||||||
id: check-configs
|
id: security-scan
|
||||||
shell: sh
|
uses: ./security-scan
|
||||||
run: |
|
|
||||||
# Initialize all flags as false
|
|
||||||
{
|
|
||||||
echo "run_gitleaks=false"
|
|
||||||
echo "run_trivy=true"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Check Gitleaks configuration and license
|
|
||||||
if [ -f ".gitleaks.toml" ] && [ -n "${{ secrets.GITLEAKS_LICENSE }}" ]; then
|
|
||||||
echo "Gitleaks config and license found"
|
|
||||||
printf '%s\n' "run_gitleaks=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "::warning::Gitleaks config or license missing - skipping Gitleaks scan"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Run actionlint
|
|
||||||
uses: raven-actions/actionlint@3a24062651993d40fed1019b58ac6fbdfbf276cc # v2.0.1
|
|
||||||
with:
|
with:
|
||||||
cache: true
|
gitleaks-license: ${{ secrets.GITLEAKS_LICENSE }}
|
||||||
fail-on-error: true
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
shellcheck: false
|
|
||||||
|
|
||||||
- name: Run Gitleaks
|
|
||||||
if: steps.check-configs.outputs.run_gitleaks == 'true'
|
|
||||||
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
|
|
||||||
with:
|
|
||||||
config-path: .gitleaks.toml
|
|
||||||
report-format: sarif
|
|
||||||
report-path: gitleaks-report.sarif
|
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
|
||||||
uses: aquasecurity/trivy-action@a11da62073708815958ea6d84f5650c78a3ef85b # master
|
|
||||||
with:
|
|
||||||
scan-type: 'fs'
|
|
||||||
scanners: 'vuln,config,secret'
|
|
||||||
format: 'sarif'
|
|
||||||
output: 'trivy-results.sarif'
|
|
||||||
severity: 'CRITICAL,HIGH'
|
|
||||||
timeout: '10m'
|
|
||||||
|
|
||||||
- name: Verify SARIF files
|
|
||||||
id: verify-sarif
|
|
||||||
shell: sh
|
|
||||||
run: |
|
|
||||||
# Initialize outputs
|
|
||||||
{
|
|
||||||
echo "has_trivy=false"
|
|
||||||
echo "has_gitleaks=false"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Check Trivy results
|
|
||||||
if [ -f "trivy-results.sarif" ]; then
|
|
||||||
if jq -e . </dev/null 2>&1 <"trivy-results.sarif"; then
|
|
||||||
printf '%s\n' "has_trivy=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "::warning::Trivy SARIF file exists but is not valid JSON"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check Gitleaks results if it ran
|
|
||||||
if [ "${{ steps.check-configs.outputs.run_gitleaks }}" = "true" ]; then
|
|
||||||
if [ -f "gitleaks-report.sarif" ]; then
|
|
||||||
if jq -e . </dev/null 2>&1 <"gitleaks-report.sarif"; then
|
|
||||||
printf '%s\n' "has_gitleaks=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "::warning::Gitleaks SARIF file exists but is not valid JSON"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Upload Trivy results
|
|
||||||
if: steps.verify-sarif.outputs.has_trivy == 'true'
|
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
|
||||||
with:
|
|
||||||
sarif_file: 'trivy-results.sarif'
|
|
||||||
category: 'trivy'
|
|
||||||
|
|
||||||
- name: Upload Gitleaks results
|
|
||||||
if: steps.verify-sarif.outputs.has_gitleaks == 'true'
|
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
|
||||||
with:
|
|
||||||
sarif_file: 'gitleaks-report.sarif'
|
|
||||||
category: 'gitleaks'
|
|
||||||
|
|
||||||
- name: Archive security reports
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
||||||
with:
|
|
||||||
name: security-reports-${{ github.run_id }}
|
|
||||||
path: |
|
|
||||||
${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'trivy-results.sarif' || '' }}
|
|
||||||
${{ steps.verify-sarif.outputs.has_gitleaks == 'true' && 'gitleaks-report.sarif' || '' }}
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
- name: Analyze Results
|
|
||||||
if: always()
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
try {
|
|
||||||
let totalIssues = 0;
|
|
||||||
let criticalIssues = 0;
|
|
||||||
|
|
||||||
const analyzeSarif = (file, tool) => {
|
|
||||||
if (!fs.existsSync(file)) {
|
|
||||||
console.log(`No results file found for ${tool}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sarif = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
||||||
return sarif.runs.reduce((acc, run) => {
|
|
||||||
if (!run.results) return acc;
|
|
||||||
|
|
||||||
const critical = run.results.filter(r =>
|
|
||||||
r.level === 'error' ||
|
|
||||||
r.level === 'critical' ||
|
|
||||||
(r.ruleId || '').toLowerCase().includes('critical')
|
|
||||||
).length;
|
|
||||||
|
|
||||||
return {
|
|
||||||
total: acc.total + run.results.length,
|
|
||||||
critical: acc.critical + critical
|
|
||||||
};
|
|
||||||
}, { total: 0, critical: 0 });
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Error analyzing ${tool} results: ${error.message}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only analyze results from tools that ran successfully
|
|
||||||
const results = {
|
|
||||||
trivy: ${{ steps.verify-sarif.outputs.has_trivy }} ?
|
|
||||||
analyzeSarif('trivy-results.sarif', 'trivy') : null,
|
|
||||||
gitleaks: ${{ steps.verify-sarif.outputs.has_gitleaks }} ?
|
|
||||||
analyzeSarif('gitleaks-report.sarif', 'gitleaks') : null
|
|
||||||
};
|
|
||||||
|
|
||||||
// Aggregate results
|
|
||||||
Object.entries(results).forEach(([tool, result]) => {
|
|
||||||
if (result) {
|
|
||||||
totalIssues += result.total;
|
|
||||||
criticalIssues += result.critical;
|
|
||||||
console.log(`${tool}: ${result.total} total, ${result.critical} critical issues`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create summary
|
|
||||||
const summary = `## Security Scan Summary
|
|
||||||
|
|
||||||
- Total Issues Found: ${totalIssues}
|
|
||||||
- Critical Issues: ${criticalIssues}
|
|
||||||
|
|
||||||
### Tool Breakdown
|
|
||||||
${Object.entries(results)
|
|
||||||
.filter(([_, r]) => r)
|
|
||||||
.map(([tool, r]) =>
|
|
||||||
`- ${tool}: ${r.total} total, ${r.critical} critical`
|
|
||||||
).join('\n')}
|
|
||||||
|
|
||||||
### Tools Run Status
|
|
||||||
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy }}
|
|
||||||
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks }}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Set output
|
|
||||||
core.setOutput('total_issues', totalIssues);
|
|
||||||
core.setOutput('critical_issues', criticalIssues);
|
|
||||||
|
|
||||||
// Add job summary
|
|
||||||
await core.summary
|
|
||||||
.addRaw(summary)
|
|
||||||
.write();
|
|
||||||
|
|
||||||
// Fail if critical issues found
|
|
||||||
if (criticalIssues > 0) {
|
|
||||||
core.setFailed(`Found ${criticalIssues} critical security issues`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(`Analysis failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Notify on Critical Issues
|
- name: Notify on Critical Issues
|
||||||
if: failure()
|
if: failure() && steps.security-scan.outputs.critical_issues != '0'
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||||
with:
|
with:
|
||||||
script: |-
|
script: |-
|
||||||
const { repo, owner } = context.repo;
|
const { repo, owner } = context.repo;
|
||||||
const critical = core.getInput('critical_issues');
|
const critical = '${{ steps.security-scan.outputs.critical_issues }}';
|
||||||
|
const total = '${{ steps.security-scan.outputs.total_issues }}';
|
||||||
|
|
||||||
const body = `🚨 Critical security issues found in GitHub Actions
|
const body = `🚨 Critical security issues found in GitHub Actions
|
||||||
|
|
||||||
${critical} critical security issues were found during the security scan.
|
${critical} critical security issues (out of ${total} total) were found during the security scan.
|
||||||
|
|
||||||
### Scan Results
|
### Scan Results
|
||||||
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'Completed' || 'Skipped/Failed' }}
|
- Actionlint: Completed
|
||||||
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks == 'true' && 'Completed' || 'Skipped' }}
|
- Trivy: ${{ steps.security-scan.outputs.has_trivy_results == 'true' && 'Completed' || 'Skipped/Failed' }}
|
||||||
|
- Gitleaks: ${{ steps.security-scan.outputs.has_gitleaks_results == 'true' && 'Completed' || 'Skipped' }}
|
||||||
|
|
||||||
[View detailed scan results](https://github.com/${owner}/${repo}/actions/runs/${context.runId})
|
[View detailed scan results](https://github.com/${owner}/${repo}/actions/runs/${context.runId})
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/build-testing-image.yml
vendored
6
.github/workflows/build-testing-image.yml
vendored
@@ -38,10 +38,10 @@ jobs:
|
|||||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Extract metadata
|
- name: Extract metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
|
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/actions
|
images: ghcr.io/${{ github.repository_owner }}/actions
|
||||||
tags: |
|
tags: |
|
||||||
|
|||||||
1
.github/workflows/codeql-new.yml
vendored
1
.github/workflows/codeql-new.yml
vendored
@@ -42,4 +42,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
language: ${{ matrix.language }}
|
language: ${{ matrix.language }}
|
||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
|
config-file: .github/codeql/codeql-config.yml
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|||||||
51
.github/workflows/codeql.yml
vendored
51
.github/workflows/codeql.yml
vendored
@@ -1,51 +0,0 @@
|
|||||||
---
|
|
||||||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
|
||||||
name: 'CodeQL'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
schedule:
|
|
||||||
- cron: '30 1 * * 0' # Run at 1:30 AM UTC every Sunday
|
|
||||||
merge_group:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language:
|
|
||||||
- 'actions'
|
|
||||||
- 'javascript'
|
|
||||||
- 'python'
|
|
||||||
|
|
||||||
steps: # Add languages used in your actions
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
queries: security-and-quality
|
|
||||||
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
|
||||||
with:
|
|
||||||
category: '/language:${{matrix.language}}'
|
|
||||||
2
.github/workflows/issue-stats.yml
vendored
2
.github/workflows/issue-stats.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
|
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Run issue-metrics tool
|
- name: Run issue-metrics tool
|
||||||
uses: github/issue-metrics@637a24e71b78bc10881e61972b19ea9ff736e14a # v3.25.2
|
uses: github/issue-metrics@67526e7bd8100b870f10b1c120780a8375777b43 # v3.25.5
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SEARCH_QUERY: 'repo:ivuorinen/actions is:issue created:${{ env.last_month }} -reason:"not planned"'
|
SEARCH_QUERY: 'repo:ivuorinen/actions is:issue created:${{ env.last_month }} -reason:"not planned"'
|
||||||
|
|||||||
55
.github/workflows/new-release.yml
vendored
55
.github/workflows/new-release.yml
vendored
@@ -21,26 +21,45 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Fetch all history and tags for comparison
|
||||||
|
|
||||||
- name: Create tag if necessary
|
- name: Create daily release
|
||||||
uses: fregante/daily-version-action@fb1a60b7c4daf1410cd755e360ebec3901e58588 # v2.1.3
|
|
||||||
id: daily-version
|
id: daily-version
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
|
||||||
- name: Create changelog text
|
VERSION="v$(date '+%Y.%m.%d')"
|
||||||
if: steps.daily-version.outputs.created
|
printf '%s\n' "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
id: changelog
|
|
||||||
uses: loopwerk/tag-changelog@941366edb8920e2071eae0449031830984b9f26e # v1.3.0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
config_file: .github/tag-changelog-config.js
|
|
||||||
|
|
||||||
- name: Create release
|
# Check if release already exists
|
||||||
if: steps.daily-version.outputs.created
|
if gh release view "$VERSION" >/dev/null 2>&1; then
|
||||||
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
|
printf '%s\n' "created=false" >> "$GITHUB_OUTPUT"
|
||||||
|
printf '%s\n' "Release $VERSION already exists - skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the most recent tag
|
||||||
|
PREVIOUS_TAG=$(git tag --sort=-version:refname | head -1)
|
||||||
|
|
||||||
|
# Check if there are any changes since the previous tag
|
||||||
|
if [ -n "$PREVIOUS_TAG" ]; then
|
||||||
|
CHANGES=$(git rev-list "$PREVIOUS_TAG"..HEAD --count)
|
||||||
|
if [ "$CHANGES" -eq 0 ]; then
|
||||||
|
printf '%s\n' "created=false" >> "$GITHUB_OUTPUT"
|
||||||
|
printf '%s\n' "No changes since $PREVIOUS_TAG - skipping release"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
printf '%s\n' "Found $CHANGES commit(s) since $PREVIOUS_TAG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create release with auto-generated changelog (also creates tag)
|
||||||
|
gh release create "$VERSION" \
|
||||||
|
--title "Release $VERSION" \
|
||||||
|
--generate-notes \
|
||||||
|
--target main
|
||||||
|
|
||||||
|
printf '%s\n' "created=true" >> "$GITHUB_OUTPUT"
|
||||||
|
printf '%s\n' "Created release $VERSION"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
|
||||||
tag: ${{ steps.daily-version.outputs.version }}
|
|
||||||
name: Release ${{ steps.daily-version.outputs.version }}
|
|
||||||
body: ${{ steps.changelog.outputs.changes }}
|
|
||||||
allowUpdates: true
|
|
||||||
|
|||||||
114
.github/workflows/pr-lint.yml
vendored
114
.github/workflows/pr-lint.yml
vendored
@@ -24,17 +24,9 @@ on:
|
|||||||
merge_group:
|
merge_group:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Apply linter fixes configuration
|
# MegaLinter configuration - these override the action's defaults
|
||||||
APPLY_FIXES: all
|
|
||||||
APPLY_FIXES_EVENT: pull_request
|
|
||||||
APPLY_FIXES_MODE: commit
|
|
||||||
|
|
||||||
# Disable linters that do not work or conflict
|
|
||||||
DISABLE_LINTERS: REPOSITORY_DEVSKIM
|
DISABLE_LINTERS: REPOSITORY_DEVSKIM
|
||||||
|
|
||||||
# Additional settings
|
|
||||||
VALIDATE_ALL_CODEBASE: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
VALIDATE_ALL_CODEBASE: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||||
GITHUB_TOKEN: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Report configuration
|
# Report configuration
|
||||||
REPORT_OUTPUT_FOLDER: megalinter-reports
|
REPORT_OUTPUT_FOLDER: megalinter-reports
|
||||||
@@ -72,111 +64,27 @@ jobs:
|
|||||||
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: MegaLinter
|
- name: Run MegaLinter
|
||||||
id: ml
|
id: pr-lint
|
||||||
uses: oxsecurity/megalinter/flavors/cupcake@62c799d895af9bcbca5eacfebca29d527f125a57 # v9.1.0
|
uses: ./pr-lint
|
||||||
|
|
||||||
- name: Check MegaLinter Results
|
|
||||||
id: check-results
|
|
||||||
if: always()
|
|
||||||
shell: sh
|
|
||||||
run: |
|
|
||||||
printf '%s\n' "status=success" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
if [ -f "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log" ]; then
|
|
||||||
if grep -q "ERROR\|CRITICAL" "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log"; then
|
|
||||||
echo "Linting errors found"
|
|
||||||
printf '%s\n' "status=failure" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "::warning::MegaLinter log file not found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Upload Reports
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
||||||
with:
|
with:
|
||||||
name: MegaLinter reports
|
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
path: |
|
username: fiximus
|
||||||
megalinter-reports
|
email: github-bot@ivuorinen.net
|
||||||
mega-linter.log
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
- name: Upload SARIF Report
|
- name: Upload SARIF Report
|
||||||
if: always() && hashFiles('megalinter-reports/sarif/*.sarif')
|
if: always() && hashFiles('megalinter-reports/sarif/*.sarif')
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
sarif_file: megalinter-reports/sarif
|
sarif_file: megalinter-reports/sarif
|
||||||
category: megalinter
|
category: megalinter
|
||||||
|
|
||||||
- name: Prepare Git for Fixes
|
- name: Check Results
|
||||||
if: steps.ml.outputs.has_updated_sources == 1
|
|
||||||
shell: sh
|
|
||||||
run: |
|
|
||||||
sudo chown -Rc $UID .git/
|
|
||||||
git config --global user.name "fiximus"
|
|
||||||
git config --global user.email "github-bot@ivuorinen.net"
|
|
||||||
|
|
||||||
- name: Create Pull Request
|
|
||||||
if: |
|
|
||||||
steps.ml.outputs.has_updated_sources == 1 &&
|
|
||||||
(env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) &&
|
|
||||||
env.APPLY_FIXES_MODE == 'pull_request' &&
|
|
||||||
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) &&
|
|
||||||
!contains(github.event.head_commit.message, 'skip fix')
|
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
|
||||||
id: cpr
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
|
|
||||||
commit-message: '[MegaLinter] Apply linters automatic fixes'
|
|
||||||
title: '[MegaLinter] Apply linters automatic fixes'
|
|
||||||
labels: bot
|
|
||||||
branch: megalinter/fixes-${{ github.ref_name }}
|
|
||||||
branch-suffix: timestamp
|
|
||||||
delete-branch: true
|
|
||||||
body: |
|
|
||||||
## MegaLinter Fixes
|
|
||||||
|
|
||||||
MegaLinter has identified and fixed code style issues.
|
|
||||||
|
|
||||||
### 🔍 Changes Made
|
|
||||||
- Automated code style fixes
|
|
||||||
- Formatting improvements
|
|
||||||
- Lint error corrections
|
|
||||||
|
|
||||||
### 📝 Notes
|
|
||||||
- Please review the changes carefully
|
|
||||||
- Run tests before merging
|
|
||||||
- Verify formatting matches project standards
|
|
||||||
|
|
||||||
> Generated automatically by MegaLinter
|
|
||||||
|
|
||||||
- name: Commit Fixes
|
|
||||||
if: |
|
|
||||||
steps.ml.outputs.has_updated_sources == 1 &&
|
|
||||||
(env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) &&
|
|
||||||
env.APPLY_FIXES_MODE == 'commit' &&
|
|
||||||
github.ref != 'refs/heads/main' &&
|
|
||||||
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) &&
|
|
||||||
!contains(github.event.head_commit.message, 'skip fix')
|
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
|
|
||||||
branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}
|
|
||||||
commit_message: |
|
|
||||||
style: apply MegaLinter fixes
|
|
||||||
|
|
||||||
[skip ci]
|
|
||||||
commit_user_name: fiximus
|
|
||||||
commit_user_email: github-bot@ivuorinen.net
|
|
||||||
push_options: --force
|
|
||||||
|
|
||||||
- name: Create Status Check
|
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const status = '${{ steps.check-results.outputs.status }}';
|
const status = '${{ steps.pr-lint.outputs.validation_status }}';
|
||||||
const conclusion = status === 'success' ? 'success' : 'failure';
|
const conclusion = status === 'success' ? 'success' : 'failure';
|
||||||
|
|
||||||
const summary = `## MegaLinter Results
|
const summary = `## MegaLinter Results
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -17,6 +17,6 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||||
- uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
- uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||||
with:
|
with:
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|||||||
2
.github/workflows/security-suite.yml
vendored
2
.github/workflows/security-suite.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
|||||||
# Record the base commit for diffing without checking it out
|
# Record the base commit for diffing without checking it out
|
||||||
# Keep PR head checked out so scanners analyze the new changes
|
# Keep PR head checked out so scanners analyze the new changes
|
||||||
BASE_REF="refs/remotes/origin-base/${{ github.event.pull_request.base.ref }}"
|
BASE_REF="refs/remotes/origin-base/${{ github.event.pull_request.base.ref }}"
|
||||||
echo "BASE_REF=${BASE_REF}" >> $GITHUB_ENV
|
echo "BASE_REF=${BASE_REF}" >> "$GITHUB_ENV"
|
||||||
echo "Base ref: ${BASE_REF}"
|
echo "Base ref: ${BASE_REF}"
|
||||||
git log -1 --oneline "${BASE_REF}"
|
git log -1 --oneline "${BASE_REF}"
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 🚀 Run stale
|
- name: 🚀 Run stale
|
||||||
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 30
|
days-before-stale: 30
|
||||||
|
|||||||
14
.github/workflows/test-actions.yml
vendored
14
.github/workflows/test-actions.yml
vendored
@@ -73,14 +73,14 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
if: always() && hashFiles('_tests/reports/test-results.sarif') != ''
|
if: always() && hashFiles('_tests/reports/test-results.sarif') != ''
|
||||||
with:
|
with:
|
||||||
sarif_file: _tests/reports/test-results.sarif
|
sarif_file: _tests/reports/test-results.sarif
|
||||||
category: github-actions-tests
|
category: github-actions-tests
|
||||||
|
|
||||||
- name: Upload unit test results
|
- name: Upload unit test results
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: unit-test-results
|
name: unit-test-results
|
||||||
@@ -125,15 +125,15 @@ jobs:
|
|||||||
shell: sh
|
shell: sh
|
||||||
run: |
|
run: |
|
||||||
if [ -d "_tests/reports/integration" ] && [ -n "$(find _tests/reports/integration -type f 2>/dev/null)" ]; then
|
if [ -d "_tests/reports/integration" ] && [ -n "$(find _tests/reports/integration -type f 2>/dev/null)" ]; then
|
||||||
printf '%s\n' "reports-found=true" >> $GITHUB_OUTPUT
|
printf '%s\n' "reports-found=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "Integration test reports found"
|
echo "Integration test reports found"
|
||||||
else
|
else
|
||||||
printf '%s\n' "reports-found=false" >> $GITHUB_OUTPUT
|
printf '%s\n' "reports-found=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "No integration test reports found"
|
echo "No integration test reports found"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload integration test results
|
- name: Upload integration test results
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
if: always() && steps.check-integration-reports.outputs.reports-found == 'true'
|
if: always() && steps.check-integration-reports.outputs.reports-found == 'true'
|
||||||
with:
|
with:
|
||||||
name: integration-test-results
|
name: integration-test-results
|
||||||
@@ -167,7 +167,7 @@ jobs:
|
|||||||
run: make test-coverage
|
run: make test-coverage
|
||||||
|
|
||||||
- name: Upload coverage report
|
- name: Upload coverage report
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: coverage-report
|
name: coverage-report
|
||||||
path: _tests/coverage/
|
path: _tests/coverage/
|
||||||
@@ -263,7 +263,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download test results
|
- name: Download test results
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
with:
|
with:
|
||||||
pattern: '*-test-results'
|
pattern: '*-test-results'
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
31
.github/workflows/version-maintenance.yml
vendored
31
.github/workflows/version-maintenance.yml
vendored
@@ -40,6 +40,29 @@ jobs:
|
|||||||
printf '%s\n' "major=v$current_year" >> "$GITHUB_OUTPUT"
|
printf '%s\n' "major=v$current_year" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Ensure Major Version Tag Exists
|
||||||
|
id: ensure-tag
|
||||||
|
shell: sh
|
||||||
|
env:
|
||||||
|
MAJOR_VERSION: ${{ steps.version.outputs.major }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
git fetch --tags --force
|
||||||
|
|
||||||
|
if git rev-list -n 1 "$MAJOR_VERSION" >/dev/null 2>&1; then
|
||||||
|
echo "Tag $MAJOR_VERSION already exists"
|
||||||
|
printf '%s\n' "created=false" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "Tag $MAJOR_VERSION not found, creating..."
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git tag -a "$MAJOR_VERSION" -m "Major version $MAJOR_VERSION"
|
||||||
|
git push origin "$MAJOR_VERSION"
|
||||||
|
echo "Created and pushed tag $MAJOR_VERSION"
|
||||||
|
printf '%s\n' "created=true" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run Action Versioning
|
- name: Run Action Versioning
|
||||||
id: action-versioning
|
id: action-versioning
|
||||||
uses: ./action-versioning
|
uses: ./action-versioning
|
||||||
@@ -49,7 +72,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: steps.action-versioning.outputs.updated == 'true'
|
if: steps.action-versioning.outputs.updated == 'true'
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
|
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
|
||||||
@@ -68,8 +91,6 @@ jobs:
|
|||||||
```bash
|
```bash
|
||||||
make check-version-refs
|
make check-version-refs
|
||||||
```
|
```
|
||||||
|
|
||||||
🤖 Auto-generated by version-maintenance workflow
|
|
||||||
branch: automated/version-update-${{ steps.version.outputs.major }}
|
branch: automated/version-update-${{ steps.version.outputs.major }}
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
labels: |
|
labels: |
|
||||||
@@ -78,7 +99,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for Annual Bump
|
- name: Check for Annual Bump
|
||||||
if: steps.action-versioning.outputs.needs-annual-bump == 'true'
|
if: steps.action-versioning.outputs.needs-annual-bump == 'true'
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
@@ -120,8 +141,6 @@ jobs:
|
|||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
make check-version-refs
|
make check-version-refs
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
🤖 Auto-generated by version-maintenance workflow
|
|
||||||
`,
|
`,
|
||||||
labels: ['maintenance', 'high-priority']
|
labels: ['maintenance', 'high-priority']
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,5 +9,6 @@
|
|||||||
"siblings_only": true
|
"siblings_only": true
|
||||||
},
|
},
|
||||||
"MD033": false,
|
"MD033": false,
|
||||||
"MD041": false
|
"MD041": false,
|
||||||
|
"MD060": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ repos:
|
|||||||
types: [markdown, python, yaml]
|
types: [markdown, python, yaml]
|
||||||
files: ^(docs/.*|README\.md|CONTRIBUTING\.md|CHANGELOG\.md|.*\.py|.*\.ya?ml)$
|
files: ^(docs/.*|README\.md|CONTRIBUTING\.md|CHANGELOG\.md|.*\.py|.*\.ya?ml)$
|
||||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||||
rev: 0.9.11
|
rev: 0.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: uv-lock
|
- id: uv-lock
|
||||||
- id: uv-sync
|
- id: uv-sync
|
||||||
@@ -44,18 +44,18 @@ repos:
|
|||||||
args: [--autofix, --no-sort-keys]
|
args: [--autofix, --no-sort-keys]
|
||||||
|
|
||||||
- repo: https://github.com/DavidAnson/markdownlint-cli2
|
- repo: https://github.com/DavidAnson/markdownlint-cli2
|
||||||
rev: v0.19.0
|
rev: v0.21.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: markdownlint-cli2
|
- id: markdownlint-cli2
|
||||||
args: [--fix]
|
args: [--fix]
|
||||||
|
|
||||||
- repo: https://github.com/adrienverge/yamllint
|
- repo: https://github.com/adrienverge/yamllint
|
||||||
rev: v1.37.1
|
rev: v1.38.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamllint
|
- id: yamllint
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.14.5
|
rev: v0.15.0
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter with auto-fix
|
# Run the linter with auto-fix
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
@@ -78,24 +78,19 @@ repos:
|
|||||||
exclude: '^_tests/.*\.sh$'
|
exclude: '^_tests/.*\.sh$'
|
||||||
|
|
||||||
- repo: https://github.com/rhysd/actionlint
|
- repo: https://github.com/rhysd/actionlint
|
||||||
rev: v1.7.8
|
rev: v1.7.10
|
||||||
hooks:
|
hooks:
|
||||||
- id: actionlint
|
- id: actionlint
|
||||||
args: ['-shellcheck=']
|
args: ['-shellcheck=']
|
||||||
|
|
||||||
- repo: https://github.com/renovatebot/pre-commit-hooks
|
|
||||||
rev: 42.19.0
|
|
||||||
hooks:
|
|
||||||
- id: renovate-config-validator
|
|
||||||
|
|
||||||
- repo: https://github.com/bridgecrewio/checkov.git
|
- repo: https://github.com/bridgecrewio/checkov.git
|
||||||
rev: '3.2.494'
|
rev: '3.2.500'
|
||||||
hooks:
|
hooks:
|
||||||
- id: checkov
|
- id: checkov
|
||||||
args:
|
args:
|
||||||
- '--quiet'
|
- '--quiet'
|
||||||
|
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
rev: v8.29.0
|
rev: v8.30.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: gitleaks
|
- id: gitleaks
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.14.0
|
3.14.3
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
- **Path**: /Users/ivuorinen/Code/ivuorinen/actions
|
- **Path**: /Users/ivuorinen/Code/ivuorinen/actions
|
||||||
- **Branch**: main
|
- **Branch**: main
|
||||||
- **External Usage**: `ivuorinen/actions/<action-name>@main`
|
- **External Usage**: `ivuorinen/actions/<action-name>@main`
|
||||||
- **Total Actions**: 43 self-contained actions
|
- **Total Actions**: 44 self-contained actions
|
||||||
|
- **Dogfooding**: Workflows use local actions (pr-lint, codeql-analysis, security-scan)
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/
|
/
|
||||||
├── <action-dirs>/ # 43 self-contained actions
|
├── <action-dirs>/ # 44 self-contained actions
|
||||||
│ ├── action.yml # Action definition
|
│ ├── action.yml # Action definition
|
||||||
│ ├── README.md # Auto-generated
|
│ ├── README.md # Auto-generated
|
||||||
│ └── CustomValidator.py # Optional validator
|
│ └── CustomValidator.py # Optional validator
|
||||||
@@ -25,12 +26,14 @@
|
|||||||
└── Makefile # Build automation
|
└── Makefile # Build automation
|
||||||
```
|
```
|
||||||
|
|
||||||
## Action Categories (43 total)
|
## Action Categories (44 total)
|
||||||
|
|
||||||
**Setup (7)**: node-setup, set-git-config, php-version-detect, python-version-detect, python-version-detect-v2, go-version-detect, dotnet-version-detect
|
**Setup (7)**: node-setup, set-git-config, php-version-detect, python-version-detect, python-version-detect-v2, go-version-detect, dotnet-version-detect
|
||||||
|
|
||||||
**Linting (13)**: ansible-lint-fix, biome-check/fix, csharp-lint-check, eslint-check/fix, go-lint, pr-lint, pre-commit, prettier-check/fix, python-lint-fix, terraform-lint-fix
|
**Linting (13)**: ansible-lint-fix, biome-check/fix, csharp-lint-check, eslint-check/fix, go-lint, pr-lint, pre-commit, prettier-check/fix, python-lint-fix, terraform-lint-fix
|
||||||
|
|
||||||
|
**Security (1)**: security-scan (actionlint, Gitleaks, Trivy scanning)
|
||||||
|
|
||||||
**Build (3)**: csharp-build, go-build, docker-build
|
**Build (3)**: csharp-build, go-build, docker-build
|
||||||
|
|
||||||
**Publishing (5)**: npm-publish, docker-publish, docker-publish-gh, docker-publish-hub, csharp-publish
|
**Publishing (5)**: npm-publish, docker-publish, docker-publish-gh, docker-publish-hub, csharp-publish
|
||||||
@@ -85,3 +88,28 @@ make test # All tests (pytest + ShellSpec)
|
|||||||
- ✅ Convention-based validation
|
- ✅ Convention-based validation
|
||||||
- ✅ Test generation system
|
- ✅ Test generation system
|
||||||
- ✅ Full backward compatibility
|
- ✅ Full backward compatibility
|
||||||
|
|
||||||
|
## Dogfooding Strategy
|
||||||
|
|
||||||
|
The repository actively dogfoods its own actions in workflows:
|
||||||
|
|
||||||
|
**Fully Dogfooded Workflows**:
|
||||||
|
|
||||||
|
- **pr-lint.yml**: Uses `./pr-lint` (was 204 lines, now 112 lines - 45% reduction)
|
||||||
|
- **action-security.yml**: Uses `./security-scan` (was 264 lines, now 82 lines - 69% reduction)
|
||||||
|
- **codeql-new.yml**: Uses `./codeql-analysis`
|
||||||
|
- **sync-labels.yml**: Uses `./sync-labels`
|
||||||
|
- **version-maintenance.yml**: Uses `./action-versioning`
|
||||||
|
|
||||||
|
**Intentionally External**:
|
||||||
|
|
||||||
|
- **build-testing-image.yml**: Uses docker/\* actions directly (needs metadata extraction)
|
||||||
|
- Core GitHub actions (checkout, upload-artifact, setup-\*) kept for standardization
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
|
||||||
|
- Early detection of action issues
|
||||||
|
- Real-world testing of actions
|
||||||
|
- Reduced workflow duplication
|
||||||
|
- Improved maintainability
|
||||||
|
- Better documentation through usage examples
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -22,9 +22,9 @@ Each action is fully self-contained and can be used independently in any GitHub
|
|||||||
|
|
||||||
## 📚 Action Catalog
|
## 📚 Action Catalog
|
||||||
|
|
||||||
This repository contains **25 reusable GitHub Actions** for CI/CD automation.
|
This repository contains **26 reusable GitHub Actions** for CI/CD automation.
|
||||||
|
|
||||||
### Quick Reference (25 Actions)
|
### Quick Reference (26 Actions)
|
||||||
|
|
||||||
| Icon | Action | Category | Description | Key Features |
|
| Icon | Action | Category | Description | Key Features |
|
||||||
|:----:|:-----------------------------------------------------|:-----------|:----------------------------------------------------------------|:---------------------------------------------|
|
|:----:|:-----------------------------------------------------|:-----------|:----------------------------------------------------------------|:---------------------------------------------|
|
||||||
@@ -34,7 +34,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
|
|||||||
| 🛡️ | [`codeql-analysis`][codeql-analysis] | Repository | Run CodeQL security analysis for a single language with conf... | Auto-detection, Token auth, Outputs |
|
| 🛡️ | [`codeql-analysis`][codeql-analysis] | Repository | Run CodeQL security analysis for a single language with conf... | Auto-detection, Token auth, Outputs |
|
||||||
| 🖼️ | [`compress-images`][compress-images] | Repository | Compress images on demand (workflow_dispatch), and at 11pm e... | Token auth, Outputs |
|
| 🖼️ | [`compress-images`][compress-images] | Repository | Compress images on demand (workflow_dispatch), and at 11pm e... | Token auth, Outputs |
|
||||||
| 📝 | [`csharp-build`][csharp-build] | Build | Builds and tests C# projects. | Caching, Auto-detection, Token auth, Outputs |
|
| 📝 | [`csharp-build`][csharp-build] | Build | Builds and tests C# projects. | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Auto-detection, Token auth, Outputs |
|
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| 📦 | [`csharp-publish`][csharp-publish] | Publishing | Publishes a C# project to GitHub Packages. | Caching, Auto-detection, Token auth, Outputs |
|
| 📦 | [`csharp-publish`][csharp-publish] | Publishing | Publishes a C# project to GitHub Packages. | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| 📦 | [`docker-build`][docker-build] | Build | Builds a Docker image for multiple architectures with enhanc... | Caching, Auto-detection, Token auth, Outputs |
|
| 📦 | [`docker-build`][docker-build] | Build | Builds a Docker image for multiple architectures with enhanc... | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| ☁️ | [`docker-publish`][docker-publish] | Publishing | Simple wrapper to publish Docker images to GitHub Packages a... | Token auth, Outputs |
|
| ☁️ | [`docker-publish`][docker-publish] | Publishing | Simple wrapper to publish Docker images to GitHub Packages a... | Token auth, Outputs |
|
||||||
@@ -49,6 +49,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
|
|||||||
| ✅ | [`prettier-lint`][prettier-lint] | Linting | Run Prettier in check or fix mode with advanced configuratio... | Caching, Auto-detection, Token auth, Outputs |
|
| ✅ | [`prettier-lint`][prettier-lint] | Linting | Run Prettier in check or fix mode with advanced configuratio... | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| 📝 | [`python-lint-fix`][python-lint-fix] | Linting | Lints and fixes Python files, commits changes, and uploads S... | Caching, Auto-detection, Token auth, Outputs |
|
| 📝 | [`python-lint-fix`][python-lint-fix] | Linting | Lints and fixes Python files, commits changes, and uploads S... | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| 📦 | [`release-monthly`][release-monthly] | Repository | Creates a release for the current month, incrementing patch ... | Token auth, Outputs |
|
| 📦 | [`release-monthly`][release-monthly] | Repository | Creates a release for the current month, incrementing patch ... | Token auth, Outputs |
|
||||||
|
| 🛡️ | [`security-scan`][security-scan] | Security | Comprehensive security scanning for GitHub Actions including... | Caching, Token auth, Outputs |
|
||||||
| 📦 | [`stale`][stale] | Repository | A GitHub Action to close stale issues and pull requests. | Token auth, Outputs |
|
| 📦 | [`stale`][stale] | Repository | A GitHub Action to close stale issues and pull requests. | Token auth, Outputs |
|
||||||
| 🏷️ | [`sync-labels`][sync-labels] | Repository | Sync labels from a YAML file to a GitHub repository | Token auth, Outputs |
|
| 🏷️ | [`sync-labels`][sync-labels] | Repository | Sync labels from a YAML file to a GitHub repository | Token auth, Outputs |
|
||||||
| 🖥️ | [`terraform-lint-fix`][terraform-lint-fix] | Linting | Lints and fixes Terraform files with advanced validation and... | Token auth, Outputs |
|
| 🖥️ | [`terraform-lint-fix`][terraform-lint-fix] | Linting | Lints and fixes Terraform files with advanced validation and... | Token auth, Outputs |
|
||||||
@@ -74,7 +75,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
|
|||||||
|:-----------------------------------------------|:------------------------------------------------------|:---------------------------------------------|:---------------------------------------------|
|
|:-----------------------------------------------|:------------------------------------------------------|:---------------------------------------------|:---------------------------------------------|
|
||||||
| 📦 [`ansible-lint-fix`][ansible-lint-fix] | Lints and fixes Ansible playbooks, commits changes... | Ansible, YAML | Caching, Token auth, Outputs |
|
| 📦 [`ansible-lint-fix`][ansible-lint-fix] | Lints and fixes Ansible playbooks, commits changes... | Ansible, YAML | Caching, Token auth, Outputs |
|
||||||
| ✅ [`biome-lint`][biome-lint] | Run Biome linter in check or fix mode | JavaScript, TypeScript, JSON | Caching, Auto-detection, Token auth, Outputs |
|
| ✅ [`biome-lint`][biome-lint] | Run Biome linter in check or fix mode | JavaScript, TypeScript, JSON | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| 📝 [`csharp-lint-check`][csharp-lint-check] | Runs linters like StyleCop or dotnet-format for C#... | C#, .NET | Auto-detection, Token auth, Outputs |
|
| 📝 [`csharp-lint-check`][csharp-lint-check] | Runs linters like StyleCop or dotnet-format for C#... | C#, .NET | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| ✅ [`eslint-lint`][eslint-lint] | Run ESLint in check or fix mode with advanced conf... | JavaScript, TypeScript | Caching, Auto-detection, Token auth, Outputs |
|
| ✅ [`eslint-lint`][eslint-lint] | Run ESLint in check or fix mode with advanced conf... | JavaScript, TypeScript | Caching, Auto-detection, Token auth, Outputs |
|
||||||
| 📝 [`go-lint`][go-lint] | Run golangci-lint with advanced configuration, cac... | Go | Caching, Token auth, Outputs |
|
| 📝 [`go-lint`][go-lint] | Run golangci-lint with advanced configuration, cac... | Go | Caching, Token auth, Outputs |
|
||||||
| ✅ [`pr-lint`][pr-lint] | Runs MegaLinter against pull requests | Conventional Commits | Caching, Auto-detection, Token auth, Outputs |
|
| ✅ [`pr-lint`][pr-lint] | Runs MegaLinter against pull requests | Conventional Commits | Caching, Auto-detection, Token auth, Outputs |
|
||||||
@@ -115,6 +116,12 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
|
|||||||
| 📦 [`stale`][stale] | A GitHub Action to close stale issues and pull req... | GitHub Actions | Token auth, Outputs |
|
| 📦 [`stale`][stale] | A GitHub Action to close stale issues and pull req... | GitHub Actions | Token auth, Outputs |
|
||||||
| 🏷️ [`sync-labels`][sync-labels] | Sync labels from a YAML file to a GitHub repositor... | YAML, GitHub | Token auth, Outputs |
|
| 🏷️ [`sync-labels`][sync-labels] | Sync labels from a YAML file to a GitHub repositor... | YAML, GitHub | Token auth, Outputs |
|
||||||
|
|
||||||
|
#### 🛡️ Security (1 action)
|
||||||
|
|
||||||
|
| Action | Description | Languages | Features |
|
||||||
|
|:-------------------------------------|:------------------------------------------------------|:----------|:-----------------------------|
|
||||||
|
| 🛡️ [`security-scan`][security-scan] | Comprehensive security scanning for GitHub Actions... | - | Caching, Token auth, Outputs |
|
||||||
|
|
||||||
#### ✅ Validation (1 action)
|
#### ✅ Validation (1 action)
|
||||||
|
|
||||||
| Action | Description | Languages | Features |
|
| Action | Description | Languages | Features |
|
||||||
@@ -131,7 +138,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
|
|||||||
| [`codeql-analysis`][codeql-analysis] | - | ✅ | ✅ | ✅ |
|
| [`codeql-analysis`][codeql-analysis] | - | ✅ | ✅ | ✅ |
|
||||||
| [`compress-images`][compress-images] | - | - | ✅ | ✅ |
|
| [`compress-images`][compress-images] | - | - | ✅ | ✅ |
|
||||||
| [`csharp-build`][csharp-build] | ✅ | ✅ | ✅ | ✅ |
|
| [`csharp-build`][csharp-build] | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`csharp-lint-check`][csharp-lint-check] | - | ✅ | ✅ | ✅ |
|
| [`csharp-lint-check`][csharp-lint-check] | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`csharp-publish`][csharp-publish] | ✅ | ✅ | ✅ | ✅ |
|
| [`csharp-publish`][csharp-publish] | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`docker-build`][docker-build] | ✅ | ✅ | ✅ | ✅ |
|
| [`docker-build`][docker-build] | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`docker-publish`][docker-publish] | - | - | ✅ | ✅ |
|
| [`docker-publish`][docker-publish] | - | - | ✅ | ✅ |
|
||||||
@@ -146,6 +153,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
|
|||||||
| [`prettier-lint`][prettier-lint] | ✅ | ✅ | ✅ | ✅ |
|
| [`prettier-lint`][prettier-lint] | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`python-lint-fix`][python-lint-fix] | ✅ | ✅ | ✅ | ✅ |
|
| [`python-lint-fix`][python-lint-fix] | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`release-monthly`][release-monthly] | - | - | ✅ | ✅ |
|
| [`release-monthly`][release-monthly] | - | - | ✅ | ✅ |
|
||||||
|
| [`security-scan`][security-scan] | ✅ | - | ✅ | ✅ |
|
||||||
| [`stale`][stale] | - | - | ✅ | ✅ |
|
| [`stale`][stale] | - | - | ✅ | ✅ |
|
||||||
| [`sync-labels`][sync-labels] | - | - | ✅ | ✅ |
|
| [`sync-labels`][sync-labels] | - | - | ✅ | ✅ |
|
||||||
| [`terraform-lint-fix`][terraform-lint-fix] | - | - | ✅ | ✅ |
|
| [`terraform-lint-fix`][terraform-lint-fix] | - | - | ✅ | ✅ |
|
||||||
@@ -224,6 +232,7 @@ All actions can be used independently in your workflows:
|
|||||||
[prettier-lint]: prettier-lint/README.md
|
[prettier-lint]: prettier-lint/README.md
|
||||||
[python-lint-fix]: python-lint-fix/README.md
|
[python-lint-fix]: python-lint-fix/README.md
|
||||||
[release-monthly]: release-monthly/README.md
|
[release-monthly]: release-monthly/README.md
|
||||||
|
[security-scan]: security-scan/README.md
|
||||||
[stale]: stale/README.md
|
[stale]: stale/README.md
|
||||||
[sync-labels]: sync-labels/README.md
|
[sync-labels]: sync-labels/README.md
|
||||||
[terraform-lint-fix]: terraform-lint-fix/README.md
|
[terraform-lint-fix]: terraform-lint-fix/README.md
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ set -euo pipefail
|
|||||||
|
|
||||||
# Source setup utilities
|
# Source setup utilities
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
||||||
# shellcheck source=_tests/framework/setup.sh
|
# shellcheck source=_tests/framework/setup.sh
|
||||||
|
# shellcheck disable=SC1091
|
||||||
source "${SCRIPT_DIR}/setup.sh"
|
source "${SCRIPT_DIR}/setup.sh"
|
||||||
|
|
||||||
# Action testing utilities
|
# Action testing utilities
|
||||||
@@ -57,6 +57,13 @@ get_action_name() {
|
|||||||
uv run "$script_dir/../shared/validation_core.py" --name "$action_file"
|
uv run "$script_dir/../shared/validation_core.py" --name "$action_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_action_runs_using() {
|
||||||
|
local action_file="$1"
|
||||||
|
local script_dir
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
uv run "$script_dir/../shared/validation_core.py" --runs-using "$action_file"
|
||||||
|
}
|
||||||
|
|
||||||
# Check if an input is required in an action.yml file
|
# Check if an input is required in an action.yml file
|
||||||
is_input_required() {
|
is_input_required() {
|
||||||
local action_file="$1"
|
local action_file="$1"
|
||||||
@@ -69,7 +76,7 @@ is_input_required() {
|
|||||||
required_status=$(uv run "$script_dir/../shared/validation_core.py" --property "$action_file" "$input_name" "required")
|
required_status=$(uv run "$script_dir/../shared/validation_core.py" --property "$action_file" "$input_name" "required")
|
||||||
|
|
||||||
# Return 0 (success) if input is required, 1 (failure) if optional
|
# Return 0 (success) if input is required, 1 (failure) if optional
|
||||||
[[ $required_status == "required" ]]
|
[[ "$required_status" == "required" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Test input validation using Python validation module
|
# Test input validation using Python validation module
|
||||||
@@ -363,5 +370,5 @@ run_action_tests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Export all functions
|
# Export all functions
|
||||||
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name is_input_required
|
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name get_action_runs_using is_input_required
|
||||||
export -f test_input_validation test_action_outputs test_external_usage measure_action_time run_action_tests
|
export -f test_input_validation test_action_outputs test_external_usage measure_action_time run_action_tests
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ import sys
|
|||||||
|
|
||||||
import yaml # pylint: disable=import-error
|
import yaml # pylint: disable=import-error
|
||||||
|
|
||||||
|
# Default value for unknown action names (matches shared.validation_core.DEFAULT_UNKNOWN)
|
||||||
|
_DEFAULT_UNKNOWN = "Unknown"
|
||||||
|
|
||||||
|
|
||||||
class ActionValidator:
|
class ActionValidator:
|
||||||
"""Handles validation of GitHub Action inputs using Python regex engine."""
|
"""Handles validation of GitHub Action inputs using Python regex engine."""
|
||||||
@@ -86,7 +89,7 @@ class ActionValidator:
|
|||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
# Check for environment variable reference (e.g., $GITHUB_TOKEN)
|
# Check for environment variable reference (e.g., $GITHUB_TOKEN)
|
||||||
if re.match(r"^\$[A-Za-z_][A-Za-z0-9_]*$", token):
|
if re.match(r"^\$[A-Za-z_]\w*$", token, re.ASCII):
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
# Check against all known token patterns
|
# Check against all known token patterns
|
||||||
@@ -330,16 +333,16 @@ def get_action_name(action_file: str) -> str:
|
|||||||
action_file: Path to the action.yml file
|
action_file: Path to the action.yml file
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Action name or "Unknown" if not found
|
Action name or _DEFAULT_UNKNOWN if not found
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with Path(action_file).open(encoding="utf-8") as f:
|
with Path(action_file).open(encoding="utf-8") as f:
|
||||||
data = yaml.safe_load(f)
|
data = yaml.safe_load(f)
|
||||||
|
|
||||||
return data.get("name", "Unknown")
|
return data.get("name", _DEFAULT_UNKNOWN)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return "Unknown"
|
return _DEFAULT_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def _show_usage():
|
def _show_usage():
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ from typing import Any
|
|||||||
|
|
||||||
import yaml # pylint: disable=import-error
|
import yaml # pylint: disable=import-error
|
||||||
|
|
||||||
|
# Default value for unknown items (used by ActionFileParser)
|
||||||
|
DEFAULT_UNKNOWN = "Unknown"
|
||||||
|
|
||||||
|
|
||||||
class ValidationCore:
|
class ValidationCore:
|
||||||
"""Core validation functionality with standardized patterns and functions."""
|
"""Core validation functionality with standardized patterns and functions."""
|
||||||
@@ -497,9 +500,9 @@ class ActionFileParser:
|
|||||||
"""Get the action name from an action.yml file."""
|
"""Get the action name from an action.yml file."""
|
||||||
try:
|
try:
|
||||||
data = ActionFileParser.load_action_file(action_file)
|
data = ActionFileParser.load_action_file(action_file)
|
||||||
return data.get("name", "Unknown")
|
return data.get("name", DEFAULT_UNKNOWN)
|
||||||
except (OSError, ValueError, yaml.YAMLError, AttributeError):
|
except (OSError, ValueError, yaml.YAMLError, AttributeError):
|
||||||
return "Unknown"
|
return DEFAULT_UNKNOWN
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_action_inputs(action_file: str) -> list[str]:
|
def get_action_inputs(action_file: str) -> list[str]:
|
||||||
@@ -521,6 +524,16 @@ class ActionFileParser:
|
|||||||
except (OSError, ValueError, yaml.YAMLError, AttributeError):
|
except (OSError, ValueError, yaml.YAMLError, AttributeError):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_action_runs_using(action_file: str) -> str:
|
||||||
|
"""Get the runs.using value from an action.yml file."""
|
||||||
|
try:
|
||||||
|
data = ActionFileParser.load_action_file(action_file)
|
||||||
|
runs = data.get("runs", {})
|
||||||
|
return runs.get("using", "unknown")
|
||||||
|
except (OSError, ValueError, yaml.YAMLError, AttributeError):
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_required_property(input_data: dict, property_name: str) -> str:
|
def _get_required_property(input_data: dict, property_name: str) -> str:
|
||||||
"""Get the required/optional property."""
|
"""Get the required/optional property."""
|
||||||
@@ -787,6 +800,11 @@ Examples:
|
|||||||
mode_group.add_argument("--inputs", metavar="ACTION_FILE", help="List action inputs")
|
mode_group.add_argument("--inputs", metavar="ACTION_FILE", help="List action inputs")
|
||||||
mode_group.add_argument("--outputs", metavar="ACTION_FILE", help="List action outputs")
|
mode_group.add_argument("--outputs", metavar="ACTION_FILE", help="List action outputs")
|
||||||
mode_group.add_argument("--name", metavar="ACTION_FILE", help="Get action name")
|
mode_group.add_argument("--name", metavar="ACTION_FILE", help="Get action name")
|
||||||
|
mode_group.add_argument(
|
||||||
|
"--runs-using",
|
||||||
|
metavar="ACTION_FILE",
|
||||||
|
help="Get action runs.using value",
|
||||||
|
)
|
||||||
mode_group.add_argument(
|
mode_group.add_argument(
|
||||||
"--validate-yaml",
|
"--validate-yaml",
|
||||||
metavar="YAML_FILE",
|
metavar="YAML_FILE",
|
||||||
@@ -834,6 +852,12 @@ def _handle_name_command(args):
|
|||||||
print(name)
|
print(name)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_runs_using_command(args):
|
||||||
|
"""Handle the runs-using command."""
|
||||||
|
runs_using = ActionFileParser.get_action_runs_using(args.runs_using)
|
||||||
|
print(runs_using)
|
||||||
|
|
||||||
|
|
||||||
def _handle_validate_yaml_command(args):
|
def _handle_validate_yaml_command(args):
|
||||||
"""Handle the validate-yaml command."""
|
"""Handle the validate-yaml command."""
|
||||||
try:
|
try:
|
||||||
@@ -853,6 +877,7 @@ def _execute_command(args):
|
|||||||
"inputs": _handle_inputs_command,
|
"inputs": _handle_inputs_command,
|
||||||
"outputs": _handle_outputs_command,
|
"outputs": _handle_outputs_command,
|
||||||
"name": _handle_name_command,
|
"name": _handle_name_command,
|
||||||
|
"runs_using": _handle_runs_using_command,
|
||||||
"validate_yaml": _handle_validate_yaml_command,
|
"validate_yaml": _handle_validate_yaml_command,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
116
_tests/unit/security-scan/validation.spec.sh
Executable file
116
_tests/unit/security-scan/validation.spec.sh
Executable file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/usr/bin/env shellspec
|
||||||
|
# Unit tests for security-scan action validation and logic
|
||||||
|
# Framework is automatically loaded via spec_helper.sh
|
||||||
|
|
||||||
|
Describe "security-scan action"
|
||||||
|
ACTION_DIR="security-scan"
|
||||||
|
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||||
|
|
||||||
|
Context "when validating token input"
|
||||||
|
It "accepts valid GitHub token"
|
||||||
|
When call validate_input_python "security-scan" "token" "ghp_123456789012345678901234567890123456"
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
It "rejects injection in token"
|
||||||
|
When call validate_input_python "security-scan" "token" "token; rm -rf /"
|
||||||
|
The status should be failure
|
||||||
|
End
|
||||||
|
|
||||||
|
It "accepts empty token (optional)"
|
||||||
|
When call validate_input_python "security-scan" "token" ""
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Context "when validating actionlint-enabled input"
|
||||||
|
It "accepts true value"
|
||||||
|
When call validate_input_python "security-scan" "actionlint-enabled" "true"
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
It "accepts false value"
|
||||||
|
When call validate_input_python "security-scan" "actionlint-enabled" "false"
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
It "rejects non-boolean value"
|
||||||
|
When call validate_input_python "security-scan" "actionlint-enabled" "maybe"
|
||||||
|
The status should be failure
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Context "when checking action.yml structure"
|
||||||
|
It "has valid YAML syntax"
|
||||||
|
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
It "has correct action name"
|
||||||
|
name=$(get_action_name "$ACTION_FILE")
|
||||||
|
When call echo "$name"
|
||||||
|
The output should equal "Security Scan"
|
||||||
|
End
|
||||||
|
|
||||||
|
It "defines all expected inputs"
|
||||||
|
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||||
|
When call echo "$inputs"
|
||||||
|
The output should include "gitleaks-license"
|
||||||
|
The output should include "gitleaks-config"
|
||||||
|
The output should include "trivy-severity"
|
||||||
|
The output should include "trivy-scanners"
|
||||||
|
The output should include "trivy-timeout"
|
||||||
|
The output should include "actionlint-enabled"
|
||||||
|
The output should include "token"
|
||||||
|
End
|
||||||
|
|
||||||
|
It "defines all expected outputs"
|
||||||
|
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||||
|
When call echo "$outputs"
|
||||||
|
The output should include "has_trivy_results"
|
||||||
|
The output should include "has_gitleaks_results"
|
||||||
|
The output should include "total_issues"
|
||||||
|
The output should include "critical_issues"
|
||||||
|
End
|
||||||
|
|
||||||
|
It "uses composite run type"
|
||||||
|
run_type=$(get_action_runs_using "$ACTION_FILE")
|
||||||
|
When call echo "$run_type"
|
||||||
|
The output should equal "composite"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Context "when validating inputs per conventions"
|
||||||
|
It "validates token against github_token convention"
|
||||||
|
When call validate_input_python "security-scan" "token" "ghp_123456789012345678901234567890123456"
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
It "validates actionlint-enabled as boolean"
|
||||||
|
When call validate_input_python "security-scan" "actionlint-enabled" "true"
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
It "rejects invalid boolean for actionlint-enabled"
|
||||||
|
When call validate_input_python "security-scan" "actionlint-enabled" "1"
|
||||||
|
The status should be failure
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Context "when testing optional inputs"
|
||||||
|
It "accepts empty gitleaks-license"
|
||||||
|
When call validate_input_python "security-scan" "gitleaks-license" ""
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
It "accepts empty token"
|
||||||
|
When call validate_input_python "security-scan" "token" ""
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
It "accepts valid gitleaks-license value"
|
||||||
|
When call validate_input_python "security-scan" "gitleaks-license" "license-key-123"
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
@@ -76,11 +76,7 @@ if ! git diff --quiet; then
|
|||||||
git commit -m "chore: bump major version from $OLD_VERSION to $NEW_VERSION
|
git commit -m "chore: bump major version from $OLD_VERSION to $NEW_VERSION
|
||||||
|
|
||||||
This commit updates all internal action references from $OLD_VERSION
|
This commit updates all internal action references from $OLD_VERSION
|
||||||
to $NEW_VERSION.
|
to $NEW_VERSION."
|
||||||
|
|
||||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
||||||
|
|
||||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
|
||||||
|
|
||||||
printf '%b' "${GREEN}✅ Committed version bump${NC}\n"
|
printf '%b' "${GREEN}✅ Committed version bump${NC}\n"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ runs:
|
|||||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_file"
|
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_file"
|
||||||
|
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
current_sha=$(echo "$line" | grep -oE '@[a-f0-9]{40}' | sed 's/@//')
|
current_sha=$(printf '%s' "$line" | grep -oE '@[a-f0-9]{40}' | sed 's/@//')
|
||||||
|
|
||||||
if [ "$current_sha" != "$TAG_SHA" ]; then
|
if [ "$current_sha" != "$TAG_SHA" ]; then
|
||||||
echo "Found outdated reference: $current_sha (should be $TAG_SHA)"
|
echo "Found outdated reference: $current_sha (should be $TAG_SHA)"
|
||||||
@@ -153,11 +153,7 @@ runs:
|
|||||||
git commit -m "chore: update action references to $MAJOR_VERSION ($TAG_SHA)" \
|
git commit -m "chore: update action references to $MAJOR_VERSION ($TAG_SHA)" \
|
||||||
-m "" \
|
-m "" \
|
||||||
-m "This commit updates all internal action references to point to the latest" \
|
-m "This commit updates all internal action references to point to the latest" \
|
||||||
-m "$MAJOR_VERSION tag SHA." \
|
-m "$MAJOR_VERSION tag SHA."
|
||||||
-m "" \
|
|
||||||
-m "🤖 Generated with [Claude Code](https://claude.com/claude-code)" \
|
|
||||||
-m "" \
|
|
||||||
-m "Co-Authored-By: Claude <noreply@anthropic.com>"
|
|
||||||
|
|
||||||
commit_sha=$(git rev-parse HEAD)
|
commit_sha=$(git rev-parse HEAD)
|
||||||
printf '%s\n' "sha=$commit_sha" >> "$GITHUB_OUTPUT"
|
printf '%s\n' "sha=$commit_sha" >> "$GITHUB_OUTPUT"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Validate Inputs
|
- name: Validate Inputs
|
||||||
id: validate
|
id: validate
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: 'ansible-lint-fix'
|
action-type: 'ansible-lint-fix'
|
||||||
token: ${{ inputs.token }}
|
token: ${{ inputs.token }}
|
||||||
@@ -75,15 +75,15 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
if: steps.check-files.outputs.files_found == 'true'
|
if: steps.check-files.outputs.files_found == 'true'
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.14'
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|
||||||
- name: Install ansible-lint
|
- name: Install ansible-lint
|
||||||
id: install-ansible-lint
|
id: install-ansible-lint
|
||||||
if: steps.check-files.outputs.files_found == 'true'
|
if: steps.check-files.outputs.files_found == 'true'
|
||||||
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3
|
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3.0.4
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 5
|
timeout_minutes: 5
|
||||||
max_attempts: ${{ inputs.max-retries }}
|
max_attempts: ${{ inputs.max-retries }}
|
||||||
@@ -122,7 +122,7 @@ runs:
|
|||||||
|
|
||||||
- name: Commit Fixes
|
- name: Commit Fixes
|
||||||
if: steps.check-files.outputs.files_found == 'true'
|
if: steps.check-files.outputs.files_found == 'true'
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||||
with:
|
with:
|
||||||
commit_message: 'style: apply ansible lint fixes'
|
commit_message: 'style: apply ansible lint fixes'
|
||||||
commit_user_name: ${{ inputs.username }}
|
commit_user_name: ${{ inputs.username }}
|
||||||
@@ -130,6 +130,6 @@ runs:
|
|||||||
|
|
||||||
- name: Upload SARIF Report
|
- name: Upload SARIF Report
|
||||||
if: steps.check-files.outputs.files_found == 'true'
|
if: steps.check-files.outputs.files_found == 'true'
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
sarif_file: ansible-lint.sarif
|
sarif_file: ansible-lint.sarif
|
||||||
|
|||||||
@@ -181,9 +181,9 @@ runs:
|
|||||||
echo "Detected package manager: $package_manager"
|
echo "Detected package manager: $package_manager"
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
shell: sh
|
shell: sh
|
||||||
@@ -212,13 +212,13 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
if: steps.detect-pm.outputs.package-manager == 'bun'
|
if: steps.detect-pm.outputs.package-manager == 'bun'
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: Cache Node Dependencies
|
- name: Cache Node Dependencies
|
||||||
id: cache
|
id: cache
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: ${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
key: ${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
||||||
@@ -331,7 +331,7 @@ runs:
|
|||||||
|
|
||||||
- name: Upload SARIF Report
|
- name: Upload SARIF Report
|
||||||
if: inputs.mode == 'check' && always()
|
if: inputs.mode == 'check' && always()
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
sarif_file: biome-report.sarif
|
sarif_file: biome-report.sarif
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ runs:
|
|||||||
|
|
||||||
- name: Commit and Push Fixes
|
- name: Commit and Push Fixes
|
||||||
if: inputs.mode == 'fix' && success()
|
if: inputs.mode == 'fix' && success()
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||||
with:
|
with:
|
||||||
commit_message: 'style: autofix Biome violations'
|
commit_message: 'style: autofix Biome violations'
|
||||||
commit_user_name: ${{ inputs.username }}
|
commit_user_name: ${{ inputs.username }}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ conventions:
|
|||||||
mode: mode_enum
|
mode: mode_enum
|
||||||
token: github_token
|
token: github_token
|
||||||
username: username
|
username: username
|
||||||
overrides: {}
|
overrides:
|
||||||
|
mode: mode_enum
|
||||||
statistics:
|
statistics:
|
||||||
total_inputs: 6
|
total_inputs: 6
|
||||||
validated_inputs: 6
|
validated_inputs: 6
|
||||||
|
|||||||
@@ -81,21 +81,13 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate threads
|
# Validate threads
|
||||||
if inputs.get("threads"):
|
if inputs.get("threads"):
|
||||||
result = self.codeql_validator.validate_threads(inputs["threads"])
|
valid &= self.validate_with(
|
||||||
for error in self.codeql_validator.errors:
|
self.codeql_validator, "validate_threads", inputs["threads"]
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.codeql_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate RAM
|
# Validate RAM
|
||||||
if inputs.get("ram"):
|
if inputs.get("ram"):
|
||||||
result = self.codeql_validator.validate_ram(inputs["ram"])
|
valid &= self.validate_with(self.codeql_validator, "validate_ram", inputs["ram"])
|
||||||
for error in self.codeql_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.codeql_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate debug mode
|
# Validate debug mode
|
||||||
if inputs.get("debug"):
|
if inputs.get("debug"):
|
||||||
@@ -226,19 +218,10 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Check for empty queries first
|
|
||||||
if not queries or not queries.strip():
|
if not queries or not queries.strip():
|
||||||
self.add_error("CodeQL queries cannot be empty")
|
self.add_error("CodeQL queries cannot be empty")
|
||||||
return False
|
return False
|
||||||
|
return self.validate_with(self.codeql_validator, "validate_codeql_queries", queries)
|
||||||
# Use the CodeQL validator
|
|
||||||
result = self.codeql_validator.validate_codeql_queries(queries)
|
|
||||||
# Copy any errors from codeql validator
|
|
||||||
for error in self.codeql_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.codeql_validator.clear_errors()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_categories(self, categories: str) -> bool:
|
def validate_categories(self, categories: str) -> bool:
|
||||||
"""Validate CodeQL categories.
|
"""Validate CodeQL categories.
|
||||||
@@ -249,14 +232,7 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Use the CodeQL validator
|
return self.validate_with(self.codeql_validator, "validate_category_format", categories)
|
||||||
result = self.codeql_validator.validate_category_format(categories)
|
|
||||||
# Copy any errors from codeql validator
|
|
||||||
for error in self.codeql_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.codeql_validator.clear_errors()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_category(self, category: str) -> bool:
|
def validate_category(self, category: str) -> bool:
|
||||||
"""Validate CodeQL category (singular).
|
"""Validate CodeQL category (singular).
|
||||||
@@ -267,14 +243,7 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Use the CodeQL validator
|
return self.validate_with(self.codeql_validator, "validate_category_format", category)
|
||||||
result = self.codeql_validator.validate_category_format(category)
|
|
||||||
# Copy any errors from codeql validator
|
|
||||||
for error in self.codeql_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.codeql_validator.clear_errors()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_config_file(self, config_file: str) -> bool:
|
def validate_config_file(self, config_file: str) -> bool:
|
||||||
"""Validate CodeQL configuration file path.
|
"""Validate CodeQL configuration file path.
|
||||||
@@ -287,21 +256,11 @@ class CustomValidator(BaseValidator):
|
|||||||
"""
|
"""
|
||||||
if not config_file or not config_file.strip():
|
if not config_file or not config_file.strip():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(config_file):
|
if self.is_github_expression(config_file):
|
||||||
return True
|
return True
|
||||||
|
return self.validate_with(
|
||||||
# Use FileValidator for yaml file validation
|
self.file_validator, "validate_yaml_file", config_file, "config-file"
|
||||||
result = self.file_validator.validate_yaml_file(config_file, "config-file")
|
)
|
||||||
|
|
||||||
# Copy any errors from file validator
|
|
||||||
for error in self.file_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_database(self, database: str) -> bool:
|
def validate_database(self, database: str) -> bool:
|
||||||
"""Validate CodeQL database path.
|
"""Validate CodeQL database path.
|
||||||
@@ -312,25 +271,13 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(database):
|
if self.is_github_expression(database):
|
||||||
return True
|
return True
|
||||||
|
result = self.validate_with(self.file_validator, "validate_file_path", database, "database")
|
||||||
# Use FileValidator for path validation
|
|
||||||
result = self.file_validator.validate_file_path(database, "database")
|
|
||||||
|
|
||||||
# Copy any errors from file validator
|
|
||||||
for error in self.file_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
|
|
||||||
# Database paths often contain the language
|
# Database paths often contain the language
|
||||||
# e.g., "codeql-database/javascript" or "/tmp/codeql_databases/python"
|
# e.g., "codeql-database/javascript" or "/tmp/codeql_databases/python"
|
||||||
# Just validate it's a reasonable path after basic validation
|
|
||||||
if result and database.startswith("/tmp/"): # noqa: S108
|
if result and database.startswith("/tmp/"): # noqa: S108
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def validate_debug(self, debug: str) -> bool:
|
def validate_debug(self, debug: str) -> bool:
|
||||||
@@ -342,20 +289,9 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(debug):
|
if self.is_github_expression(debug):
|
||||||
return True
|
return True
|
||||||
|
return self.validate_with(self.boolean_validator, "validate_boolean", debug, "debug")
|
||||||
# Use BooleanValidator
|
|
||||||
result = self.boolean_validator.validate_boolean(debug, "debug")
|
|
||||||
|
|
||||||
# Copy any errors from boolean validator
|
|
||||||
for error in self.boolean_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_upload_database(self, upload: str) -> bool:
|
def validate_upload_database(self, upload: str) -> bool:
|
||||||
"""Validate upload-database setting.
|
"""Validate upload-database setting.
|
||||||
@@ -366,20 +302,11 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(upload):
|
if self.is_github_expression(upload):
|
||||||
return True
|
return True
|
||||||
|
return self.validate_with(
|
||||||
# Use BooleanValidator
|
self.boolean_validator, "validate_boolean", upload, "upload-database"
|
||||||
result = self.boolean_validator.validate_boolean(upload, "upload-database")
|
)
|
||||||
|
|
||||||
# Copy any errors from boolean validator
|
|
||||||
for error in self.boolean_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_upload_sarif(self, upload: str) -> bool:
|
def validate_upload_sarif(self, upload: str) -> bool:
|
||||||
"""Validate upload-sarif setting.
|
"""Validate upload-sarif setting.
|
||||||
@@ -390,20 +317,11 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(upload):
|
if self.is_github_expression(upload):
|
||||||
return True
|
return True
|
||||||
|
return self.validate_with(
|
||||||
# Use BooleanValidator
|
self.boolean_validator, "validate_boolean", upload, "upload-sarif"
|
||||||
result = self.boolean_validator.validate_boolean(upload, "upload-sarif")
|
)
|
||||||
|
|
||||||
# Copy any errors from boolean validator
|
|
||||||
for error in self.boolean_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_packs(self, packs: str) -> bool:
|
def validate_packs(self, packs: str) -> bool:
|
||||||
"""Validate CodeQL packs.
|
"""Validate CodeQL packs.
|
||||||
@@ -487,16 +405,9 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Use the TokenValidator for proper validation
|
return self.validate_with(
|
||||||
result = self.token_validator.validate_github_token(token, required=False)
|
self.token_validator, "validate_github_token", token, required=False
|
||||||
|
)
|
||||||
# Copy any errors from token validator
|
|
||||||
for error in self.token_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_token(self, token: str) -> bool:
|
def validate_token(self, token: str) -> bool:
|
||||||
"""Validate GitHub token.
|
"""Validate GitHub token.
|
||||||
@@ -507,21 +418,12 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Check for empty token
|
|
||||||
if not token or not token.strip():
|
if not token or not token.strip():
|
||||||
self.add_error("Input 'token' is missing or empty")
|
self.add_error("Input 'token' is missing or empty")
|
||||||
return False
|
return False
|
||||||
|
return self.validate_with(
|
||||||
# Use the TokenValidator for proper validation
|
self.token_validator, "validate_github_token", token, required=True
|
||||||
result = self.token_validator.validate_github_token(token, required=True)
|
)
|
||||||
|
|
||||||
# Copy any errors from token validator
|
|
||||||
for error in self.token_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_working_directory(self, directory: str) -> bool:
|
def validate_working_directory(self, directory: str) -> bool:
|
||||||
"""Validate working directory path.
|
"""Validate working directory path.
|
||||||
@@ -532,20 +434,11 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(directory):
|
if self.is_github_expression(directory):
|
||||||
return True
|
return True
|
||||||
|
return self.validate_with(
|
||||||
# Use FileValidator for path validation
|
self.file_validator, "validate_file_path", directory, "working-directory"
|
||||||
result = self.file_validator.validate_file_path(directory, "working-directory")
|
)
|
||||||
|
|
||||||
# Copy any errors from file validator
|
|
||||||
for error in self.file_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_upload_results(self, value: str) -> bool:
|
def validate_upload_results(self, value: str) -> bool:
|
||||||
"""Validate upload-results boolean value.
|
"""Validate upload-results boolean value.
|
||||||
@@ -556,27 +449,14 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Check for empty
|
|
||||||
if not value or not value.strip():
|
if not value or not value.strip():
|
||||||
self.add_error("upload-results cannot be empty")
|
self.add_error("upload-results cannot be empty")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(value):
|
if self.is_github_expression(value):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check for uppercase TRUE/FALSE first
|
|
||||||
if value in ["TRUE", "FALSE"]:
|
if value in ["TRUE", "FALSE"]:
|
||||||
self.add_error("Must be lowercase 'true' or 'false'")
|
self.add_error("Must be lowercase 'true' or 'false'")
|
||||||
return False
|
return False
|
||||||
|
return self.validate_with(
|
||||||
# Use BooleanValidator for normal validation
|
self.boolean_validator, "validate_boolean", value, "upload-results"
|
||||||
result = self.boolean_validator.validate_boolean(value, "upload-results")
|
)
|
||||||
|
|
||||||
# Copy any errors from boolean validator
|
|
||||||
for error in self.boolean_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ runs:
|
|||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- name: Validate inputs
|
- name: Validate inputs
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: codeql-analysis
|
action-type: codeql-analysis
|
||||||
language: ${{ inputs.language }}
|
language: ${{ inputs.language }}
|
||||||
@@ -186,7 +186,7 @@ runs:
|
|||||||
echo "Using build mode: $build_mode"
|
echo "Using build mode: $build_mode"
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
languages: ${{ inputs.language }}
|
languages: ${{ inputs.language }}
|
||||||
queries: ${{ inputs.queries }}
|
queries: ${{ inputs.queries }}
|
||||||
@@ -199,12 +199,12 @@ runs:
|
|||||||
threads: ${{ inputs.threads }}
|
threads: ${{ inputs.threads }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/autobuild@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
if: ${{ steps.set-build-mode.outputs.build-mode == 'autobuild' }}
|
if: ${{ steps.set-build-mode.outputs.build-mode == 'autobuild' }}
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
id: analysis
|
id: analysis
|
||||||
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
category: ${{ steps.set-category.outputs.category }}
|
category: ${{ steps.set-category.outputs.category }}
|
||||||
upload: ${{ inputs.upload-results }}
|
upload: ${{ inputs.upload-results }}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ conventions:
|
|||||||
packs: codeql_packs
|
packs: codeql_packs
|
||||||
queries: codeql_queries
|
queries: codeql_queries
|
||||||
ram: numeric_range_256_32768
|
ram: numeric_range_256_32768
|
||||||
skip-queries: codeql_queries
|
skip-queries: boolean
|
||||||
source-root: file_path
|
source-root: file_path
|
||||||
threads: numeric_range_1_128
|
threads: numeric_range_1_128
|
||||||
token: github_token
|
token: github_token
|
||||||
@@ -51,6 +51,7 @@ overrides:
|
|||||||
build-mode: codeql_build_mode
|
build-mode: codeql_build_mode
|
||||||
category: category_format
|
category: category_format
|
||||||
config: codeql_config
|
config: codeql_config
|
||||||
|
language: codeql_language
|
||||||
output: file_path
|
output: file_path
|
||||||
packs: codeql_packs
|
packs: codeql_packs
|
||||||
queries: codeql_queries
|
queries: codeql_queries
|
||||||
|
|||||||
@@ -36,47 +36,35 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate optional inputs
|
# Validate optional inputs
|
||||||
if inputs.get("image-quality"):
|
if inputs.get("image-quality"):
|
||||||
result = self.numeric_validator.validate_numeric_range(
|
valid &= self.validate_with(
|
||||||
inputs["image-quality"], min_val=0, max_val=100
|
self.numeric_validator,
|
||||||
|
"validate_numeric_range",
|
||||||
|
inputs["image-quality"],
|
||||||
|
min_val=0,
|
||||||
|
max_val=100,
|
||||||
)
|
)
|
||||||
for error in self.numeric_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.numeric_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
if inputs.get("png-quality"):
|
if inputs.get("png-quality"):
|
||||||
result = self.numeric_validator.validate_numeric_range(
|
valid &= self.validate_with(
|
||||||
inputs["png-quality"], min_val=0, max_val=100
|
self.numeric_validator,
|
||||||
|
"validate_numeric_range",
|
||||||
|
inputs["png-quality"],
|
||||||
|
min_val=0,
|
||||||
|
max_val=100,
|
||||||
)
|
)
|
||||||
for error in self.numeric_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.numeric_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
if inputs.get("directory"):
|
if inputs.get("directory"):
|
||||||
result = self.file_validator.validate_file_path(inputs["directory"], "directory")
|
valid &= self.validate_with(
|
||||||
for error in self.file_validator.errors:
|
self.file_validator, "validate_file_path", inputs["directory"], "directory"
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
if inputs.get("ignore-paths"):
|
if inputs.get("ignore-paths"):
|
||||||
# Validate for injection
|
valid &= self.validate_with(
|
||||||
result = self.security_validator.validate_no_injection(
|
self.security_validator,
|
||||||
inputs["ignore-paths"], "ignore-paths"
|
"validate_no_injection",
|
||||||
|
inputs["ignore-paths"],
|
||||||
|
"ignore-paths",
|
||||||
)
|
)
|
||||||
for error in self.security_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.security_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ runs:
|
|||||||
|
|
||||||
- name: Create New Pull Request If Needed
|
- name: Create New Pull Request If Needed
|
||||||
if: steps.calibre.outputs.markdown != ''
|
if: steps.calibre.outputs.markdown != ''
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||||
with:
|
with:
|
||||||
token: ${{ inputs.token }}
|
token: ${{ inputs.token }}
|
||||||
title: 'chore: compress images'
|
title: 'chore: compress images'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Validation rules for compress-images action
|
# Validation rules for compress-images action
|
||||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||||
# Schema version: 1.0
|
# Schema version: 1.0
|
||||||
# Coverage: 86% (6/7 inputs)
|
# Coverage: 100% (7/7 inputs)
|
||||||
#
|
#
|
||||||
# This file defines validation rules for the compress-images GitHub Action.
|
# This file defines validation rules for the compress-images GitHub Action.
|
||||||
# Rules are automatically applied by validate-inputs action when this
|
# Rules are automatically applied by validate-inputs action when this
|
||||||
@@ -24,6 +24,7 @@ optional_inputs:
|
|||||||
- working-directory
|
- working-directory
|
||||||
conventions:
|
conventions:
|
||||||
email: email
|
email: email
|
||||||
|
ignore-paths: path_list
|
||||||
image-quality: numeric_range_0_100
|
image-quality: numeric_range_0_100
|
||||||
png-quality: numeric_range_0_100
|
png-quality: numeric_range_0_100
|
||||||
token: github_token
|
token: github_token
|
||||||
@@ -32,10 +33,10 @@ conventions:
|
|||||||
overrides: {}
|
overrides: {}
|
||||||
statistics:
|
statistics:
|
||||||
total_inputs: 7
|
total_inputs: 7
|
||||||
validated_inputs: 6
|
validated_inputs: 7
|
||||||
skipped_inputs: 0
|
skipped_inputs: 0
|
||||||
coverage_percentage: 86
|
coverage_percentage: 100
|
||||||
validation_coverage: 86
|
validation_coverage: 100
|
||||||
auto_detected: true
|
auto_detected: true
|
||||||
manual_review_required: false
|
manual_review_required: false
|
||||||
quality_indicators:
|
quality_indicators:
|
||||||
|
|||||||
@@ -148,14 +148,14 @@ runs:
|
|||||||
echo "Final detected .NET version: $detected_version" >&2
|
echo "Final detected .NET version: $detected_version" >&2
|
||||||
|
|
||||||
- name: Setup .NET SDK
|
- name: Setup .NET SDK
|
||||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }}
|
dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }}
|
||||||
cache: true
|
cache: true
|
||||||
cache-dependency-path: '**/packages.lock.json'
|
cache-dependency-path: '**/packages.lock.json'
|
||||||
|
|
||||||
- name: Restore Dependencies
|
- name: Restore Dependencies
|
||||||
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3
|
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3.0.4
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: ${{ inputs.max-retries }}
|
max_attempts: ${{ inputs.max-retries }}
|
||||||
@@ -203,7 +203,7 @@ runs:
|
|||||||
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: csharp-test-results
|
name: csharp-test-results
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ runs:
|
|||||||
echo "Final detected .NET version: $detected_version" >&2
|
echo "Final detected .NET version: $detected_version" >&2
|
||||||
|
|
||||||
- name: Setup .NET SDK
|
- name: Setup .NET SDK
|
||||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }}
|
dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }}
|
||||||
cache: true
|
cache: true
|
||||||
@@ -206,6 +206,6 @@ runs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload SARIF Report
|
- name: Upload SARIF Report
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
sarif_file: dotnet-format.sarif
|
sarif_file: dotnet-format.sarif
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ runs:
|
|||||||
|
|
||||||
- name: Validate Inputs
|
- name: Validate Inputs
|
||||||
id: validate
|
id: validate
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: 'csharp-publish'
|
action-type: 'csharp-publish'
|
||||||
token: ${{ inputs.token }}
|
token: ${{ inputs.token }}
|
||||||
@@ -162,14 +162,14 @@ runs:
|
|||||||
echo "Final detected .NET version: $detected_version" >&2
|
echo "Final detected .NET version: $detected_version" >&2
|
||||||
|
|
||||||
- name: Setup .NET SDK
|
- name: Setup .NET SDK
|
||||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ inputs.dotnet-version || steps.detect-dotnet-version.outputs.detected-version }}
|
dotnet-version: ${{ inputs.dotnet-version || steps.detect-dotnet-version.outputs.detected-version }}
|
||||||
cache: true
|
cache: true
|
||||||
cache-dependency-path: '**/packages.lock.json'
|
cache-dependency-path: '**/packages.lock.json'
|
||||||
|
|
||||||
- name: Restore Dependencies
|
- name: Restore Dependencies
|
||||||
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3
|
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3.0.4
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: ${{ inputs.max-retries }}
|
max_attempts: ${{ inputs.max-retries }}
|
||||||
|
|||||||
@@ -65,35 +65,24 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate image name
|
# Validate image name
|
||||||
if inputs.get("image-name"):
|
if inputs.get("image-name"):
|
||||||
result = self.docker_validator.validate_image_name(inputs["image-name"], "image-name")
|
valid &= self.validate_with(
|
||||||
# Propagate errors from docker validator
|
self.docker_validator, "validate_image_name", inputs["image-name"], "image-name"
|
||||||
for error in self.docker_validator.errors:
|
)
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.docker_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate tag (singular - as per action.yml)
|
# Validate tag (singular - as per action.yml)
|
||||||
if inputs.get("tag"):
|
if inputs.get("tag"):
|
||||||
result = self.docker_validator.validate_docker_tag(inputs["tag"], "tag")
|
valid &= self.validate_with(
|
||||||
# Propagate errors
|
self.docker_validator, "validate_docker_tag", inputs["tag"], "tag"
|
||||||
for error in self.docker_validator.errors:
|
)
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.docker_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate architectures/platforms
|
# Validate architectures/platforms
|
||||||
if inputs.get("architectures"):
|
if inputs.get("architectures"):
|
||||||
result = self.docker_validator.validate_architectures(
|
valid &= self.validate_with(
|
||||||
inputs["architectures"], "architectures"
|
self.docker_validator,
|
||||||
|
"validate_architectures",
|
||||||
|
inputs["architectures"],
|
||||||
|
"architectures",
|
||||||
)
|
)
|
||||||
# Propagate errors
|
|
||||||
for error in self.docker_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.docker_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate build arguments
|
# Validate build arguments
|
||||||
if inputs.get("build-args"):
|
if inputs.get("build-args"):
|
||||||
@@ -101,12 +90,9 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate push flag
|
# Validate push flag
|
||||||
if inputs.get("push"):
|
if inputs.get("push"):
|
||||||
result = self.boolean_validator.validate_optional_boolean(inputs["push"], "push")
|
valid &= self.validate_with(
|
||||||
for error in self.boolean_validator.errors:
|
self.boolean_validator, "validate_optional_boolean", inputs["push"], "push"
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate cache settings
|
# Validate cache settings
|
||||||
if inputs.get("cache-from"):
|
if inputs.get("cache-from"):
|
||||||
@@ -117,22 +103,35 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate cache-mode
|
# Validate cache-mode
|
||||||
if inputs.get("cache-mode"):
|
if inputs.get("cache-mode"):
|
||||||
valid &= self.validate_cache_mode(inputs["cache-mode"])
|
valid &= self.validate_enum(
|
||||||
|
inputs["cache-mode"],
|
||||||
|
"cache-mode",
|
||||||
|
["min", "max", "inline"],
|
||||||
|
case_sensitive=True,
|
||||||
|
)
|
||||||
|
|
||||||
# Validate buildx-version
|
# Validate buildx-version
|
||||||
if inputs.get("buildx-version"):
|
if inputs.get("buildx-version"):
|
||||||
valid &= self.validate_buildx_version(inputs["buildx-version"])
|
version = inputs["buildx-version"]
|
||||||
|
# Allow 'latest' as special value
|
||||||
|
if version != "latest" and not self.is_github_expression(version):
|
||||||
|
valid &= self.validate_with(
|
||||||
|
self.version_validator,
|
||||||
|
"validate_semantic_version",
|
||||||
|
version,
|
||||||
|
"buildx-version",
|
||||||
|
)
|
||||||
|
|
||||||
# Validate parallel-builds
|
# Validate parallel-builds
|
||||||
if inputs.get("parallel-builds"):
|
if inputs.get("parallel-builds"):
|
||||||
result = self.numeric_validator.validate_numeric_range(
|
valid &= self.validate_with(
|
||||||
inputs["parallel-builds"], min_val=0, max_val=16, name="parallel-builds"
|
self.numeric_validator,
|
||||||
|
"validate_numeric_range",
|
||||||
|
inputs["parallel-builds"],
|
||||||
|
min_val=0,
|
||||||
|
max_val=16,
|
||||||
|
name="parallel-builds",
|
||||||
)
|
)
|
||||||
for error in self.numeric_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.numeric_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate boolean flags
|
# Validate boolean flags
|
||||||
for bool_input in [
|
for bool_input in [
|
||||||
@@ -144,29 +143,32 @@ class CustomValidator(BaseValidator):
|
|||||||
"auto-detect-platforms",
|
"auto-detect-platforms",
|
||||||
]:
|
]:
|
||||||
if inputs.get(bool_input):
|
if inputs.get(bool_input):
|
||||||
result = self.boolean_validator.validate_optional_boolean(
|
valid &= self.validate_with(
|
||||||
inputs[bool_input], bool_input
|
self.boolean_validator,
|
||||||
|
"validate_optional_boolean",
|
||||||
|
inputs[bool_input],
|
||||||
|
bool_input,
|
||||||
)
|
)
|
||||||
for error in self.boolean_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate sbom-format
|
# Validate sbom-format
|
||||||
if inputs.get("sbom-format"):
|
if inputs.get("sbom-format"):
|
||||||
valid &= self.validate_sbom_format(inputs["sbom-format"])
|
valid &= self.validate_enum(
|
||||||
|
inputs["sbom-format"],
|
||||||
|
"sbom-format",
|
||||||
|
["spdx-json", "cyclonedx-json", "syft-json"],
|
||||||
|
case_sensitive=True,
|
||||||
|
)
|
||||||
|
|
||||||
# Validate max-retries
|
# Validate max-retries
|
||||||
if inputs.get("max-retries"):
|
if inputs.get("max-retries"):
|
||||||
result = self.numeric_validator.validate_numeric_range(
|
valid &= self.validate_with(
|
||||||
inputs["max-retries"], min_val=0, max_val=10, name="max-retries"
|
self.numeric_validator,
|
||||||
|
"validate_numeric_range",
|
||||||
|
inputs["max-retries"],
|
||||||
|
min_val=0,
|
||||||
|
max_val=10,
|
||||||
|
name="max-retries",
|
||||||
)
|
)
|
||||||
for error in self.numeric_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.numeric_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
@@ -209,19 +211,11 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(dockerfile):
|
if self.is_github_expression(dockerfile):
|
||||||
return True
|
return True
|
||||||
|
return self.validate_with(
|
||||||
# Use file validator for path validation
|
self.file_validator, "validate_file_path", dockerfile, "dockerfile"
|
||||||
result = self.file_validator.validate_file_path(dockerfile, "dockerfile")
|
)
|
||||||
# Propagate errors
|
|
||||||
for error in self.file_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_context(self, context: str) -> bool:
|
def validate_context(self, context: str) -> bool:
|
||||||
"""Validate build context path.
|
"""Validate build context path.
|
||||||
@@ -245,10 +239,9 @@ class CustomValidator(BaseValidator):
|
|||||||
# We allow path traversal for context as Docker needs to access parent directories
|
# We allow path traversal for context as Docker needs to access parent directories
|
||||||
# Only check for command injection patterns like ; | ` $()
|
# Only check for command injection patterns like ; | ` $()
|
||||||
dangerous_chars = [";", "|", "`", "$(", "&&", "||"]
|
dangerous_chars = [";", "|", "`", "$(", "&&", "||"]
|
||||||
for char in dangerous_chars:
|
if any(char in context for char in dangerous_chars):
|
||||||
if char in context:
|
self.add_error(f"Command injection detected in context: {context}")
|
||||||
self.add_error(f"Command injection detected in context: {context}")
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -261,15 +254,9 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Use docker validator for architectures
|
return self.validate_with(
|
||||||
result = self.docker_validator.validate_architectures(platforms, "platforms")
|
self.docker_validator, "validate_architectures", platforms, "platforms"
|
||||||
# Propagate errors
|
)
|
||||||
for error in self.docker_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.docker_validator.clear_errors()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def validate_build_args(self, build_args: str) -> bool:
|
def validate_build_args(self, build_args: str) -> bool:
|
||||||
"""Validate build arguments.
|
"""Validate build arguments.
|
||||||
@@ -353,78 +340,3 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Check for security issues
|
# Check for security issues
|
||||||
return self.validate_security_patterns(cache_to, "cache-to")
|
return self.validate_security_patterns(cache_to, "cache-to")
|
||||||
|
|
||||||
def validate_cache_mode(self, cache_mode: str) -> bool:
|
|
||||||
"""Validate cache mode.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
cache_mode: Cache mode value
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if valid, False otherwise
|
|
||||||
"""
|
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(cache_mode):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Valid cache modes
|
|
||||||
valid_modes = ["min", "max", "inline"]
|
|
||||||
if cache_mode.lower() not in valid_modes:
|
|
||||||
self.add_error(f"Invalid cache-mode: {cache_mode}. Must be one of: min, max, inline")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_buildx_version(self, version: str) -> bool:
|
|
||||||
"""Validate buildx version.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
version: Buildx version
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if valid, False otherwise
|
|
||||||
"""
|
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(version):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Allow 'latest'
|
|
||||||
if version == "latest":
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check for security issues (semicolon injection etc)
|
|
||||||
if not self.validate_security_patterns(version, "buildx-version"):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Basic version format validation (e.g., 0.12.0, v0.12.0)
|
|
||||||
import re
|
|
||||||
|
|
||||||
if not re.match(r"^v?\d+\.\d+(\.\d+)?$", version):
|
|
||||||
self.add_error(f"Invalid buildx-version format: {version}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_sbom_format(self, sbom_format: str) -> bool:
|
|
||||||
"""Validate SBOM format.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sbom_format: SBOM format value
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if valid, False otherwise
|
|
||||||
"""
|
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(sbom_format):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Valid SBOM formats
|
|
||||||
valid_formats = ["spdx-json", "cyclonedx-json", "syft-json"]
|
|
||||||
if sbom_format.lower() not in valid_formats:
|
|
||||||
self.add_error(
|
|
||||||
f"Invalid sbom-format: {sbom_format}. "
|
|
||||||
"Must be one of: spdx-json, cyclonedx-json, syft-json"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ runs:
|
|||||||
|
|
||||||
- name: Validate Inputs
|
- name: Validate Inputs
|
||||||
id: validate
|
id: validate
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: 'docker-build'
|
action-type: 'docker-build'
|
||||||
image-name: ${{ inputs.image-name }}
|
image-name: ${{ inputs.image-name }}
|
||||||
@@ -175,7 +175,7 @@ runs:
|
|||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||||
with:
|
with:
|
||||||
version: ${{ inputs.buildx-version }}
|
version: ${{ inputs.buildx-version }}
|
||||||
platforms: ${{ inputs.architectures }}
|
platforms: ${{ inputs.architectures }}
|
||||||
@@ -321,7 +321,7 @@ runs:
|
|||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: ${{ inputs.push == 'true' }}
|
if: ${{ inputs.push == 'true' }}
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Validation rules for docker-build action
|
# Validation rules for docker-build action
|
||||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||||
# Schema version: 1.0
|
# Schema version: 1.0
|
||||||
# Coverage: 63% (17/27 inputs)
|
# Coverage: 100% (27/27 inputs)
|
||||||
#
|
#
|
||||||
# This file defines validation rules for the docker-build GitHub Action.
|
# This file defines validation rules for the docker-build GitHub Action.
|
||||||
# Rules are automatically applied by validate-inputs action when this
|
# Rules are automatically applied by validate-inputs action when this
|
||||||
@@ -45,17 +45,27 @@ optional_inputs:
|
|||||||
conventions:
|
conventions:
|
||||||
architectures: docker_architectures
|
architectures: docker_architectures
|
||||||
auto-detect-platforms: docker_architectures
|
auto-detect-platforms: docker_architectures
|
||||||
|
build-args: key_value_list
|
||||||
|
build-contexts: key_value_list
|
||||||
buildkit-version: semantic_version
|
buildkit-version: semantic_version
|
||||||
buildx-version: semantic_version
|
buildx-version: semantic_version
|
||||||
cache-mode: boolean
|
cache-export: cache_config
|
||||||
|
cache-from: cache_config
|
||||||
|
cache-import: cache_config
|
||||||
|
cache-mode: cache_mode
|
||||||
|
context: file_path
|
||||||
dockerfile: file_path
|
dockerfile: file_path
|
||||||
dry-run: boolean
|
dry-run: boolean
|
||||||
image-name: docker_image_name
|
image-name: docker_image_name
|
||||||
max-retries: numeric_range_1_10
|
max-retries: numeric_range_1_10
|
||||||
|
network: network_mode
|
||||||
parallel-builds: numeric_range_0_16
|
parallel-builds: numeric_range_0_16
|
||||||
|
platform-build-args: json_format
|
||||||
platform-fallback: docker_architectures
|
platform-fallback: docker_architectures
|
||||||
sbom-format: report_format
|
push: boolean
|
||||||
|
sbom-format: sbom_format
|
||||||
scan-image: boolean
|
scan-image: boolean
|
||||||
|
secrets: key_value_list
|
||||||
sign-image: boolean
|
sign-image: boolean
|
||||||
tag: docker_tag
|
tag: docker_tag
|
||||||
token: github_token
|
token: github_token
|
||||||
@@ -65,12 +75,12 @@ overrides:
|
|||||||
sbom-format: sbom_format
|
sbom-format: sbom_format
|
||||||
statistics:
|
statistics:
|
||||||
total_inputs: 27
|
total_inputs: 27
|
||||||
validated_inputs: 17
|
validated_inputs: 27
|
||||||
skipped_inputs: 0
|
skipped_inputs: 0
|
||||||
coverage_percentage: 63
|
coverage_percentage: 100
|
||||||
validation_coverage: 63
|
validation_coverage: 100
|
||||||
auto_detected: true
|
auto_detected: true
|
||||||
manual_review_required: true
|
manual_review_required: false
|
||||||
quality_indicators:
|
quality_indicators:
|
||||||
has_required_inputs: true
|
has_required_inputs: true
|
||||||
has_token_validation: true
|
has_token_validation: true
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ This validator handles Docker publish-specific validation including:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Add validate-inputs directory to path to import validators
|
# Add validate-inputs directory to path to import validators
|
||||||
@@ -58,12 +59,9 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate platforms
|
# Validate platforms
|
||||||
if inputs.get("platforms"):
|
if inputs.get("platforms"):
|
||||||
result = self.docker_validator.validate_architectures(inputs["platforms"], "platforms")
|
valid &= self.validate_with(
|
||||||
for error in self.docker_validator.errors:
|
self.docker_validator, "validate_architectures", inputs["platforms"], "platforms"
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.docker_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate boolean flags
|
# Validate boolean flags
|
||||||
for bool_input in [
|
for bool_input in [
|
||||||
@@ -74,18 +72,18 @@ class CustomValidator(BaseValidator):
|
|||||||
"verbose",
|
"verbose",
|
||||||
]:
|
]:
|
||||||
if inputs.get(bool_input):
|
if inputs.get(bool_input):
|
||||||
result = self.boolean_validator.validate_optional_boolean(
|
valid &= self.validate_with(
|
||||||
inputs[bool_input], bool_input
|
self.boolean_validator,
|
||||||
|
"validate_optional_boolean",
|
||||||
|
inputs[bool_input],
|
||||||
|
bool_input,
|
||||||
)
|
)
|
||||||
for error in self.boolean_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate cache-mode
|
# Validate cache-mode
|
||||||
if inputs.get("cache-mode"):
|
if inputs.get("cache-mode"):
|
||||||
valid &= self.validate_cache_mode(inputs["cache-mode"])
|
valid &= self.validate_enum(
|
||||||
|
inputs["cache-mode"], "cache-mode", ["min", "max", "inline"]
|
||||||
|
)
|
||||||
|
|
||||||
# Validate buildx-version
|
# Validate buildx-version
|
||||||
if inputs.get("buildx-version"):
|
if inputs.get("buildx-version"):
|
||||||
@@ -96,24 +94,18 @@ class CustomValidator(BaseValidator):
|
|||||||
valid &= self.validate_username(inputs["dockerhub-username"])
|
valid &= self.validate_username(inputs["dockerhub-username"])
|
||||||
|
|
||||||
if inputs.get("dockerhub-password"):
|
if inputs.get("dockerhub-password"):
|
||||||
# Use token validator for password/token
|
valid &= self.validate_with(
|
||||||
result = self.token_validator.validate_docker_token(
|
self.token_validator,
|
||||||
inputs["dockerhub-password"], "dockerhub-password"
|
"validate_docker_token",
|
||||||
|
inputs["dockerhub-password"],
|
||||||
|
"dockerhub-password",
|
||||||
)
|
)
|
||||||
for error in self.token_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
# Validate github-token
|
# Validate github-token
|
||||||
if inputs.get("github-token"):
|
if inputs.get("github-token"):
|
||||||
result = self.token_validator.validate_github_token(inputs["github-token"])
|
valid &= self.validate_with(
|
||||||
for error in self.token_validator.errors:
|
self.token_validator, "validate_github_token", inputs["github-token"]
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
valid &= result
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
@@ -156,40 +148,7 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Allow GitHub Actions expressions
|
return self.validate_enum(registry, "registry", ["dockerhub", "github", "both"])
|
||||||
if self.is_github_expression(registry):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Valid registry values according to action description
|
|
||||||
valid_registries = ["dockerhub", "github", "both"]
|
|
||||||
if registry.lower() not in valid_registries:
|
|
||||||
self.add_error(
|
|
||||||
f"Invalid registry: {registry}. Must be one of: dockerhub, github, or both"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_cache_mode(self, cache_mode: str) -> bool:
|
|
||||||
"""Validate cache mode.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
cache_mode: Cache mode value
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if valid, False otherwise
|
|
||||||
"""
|
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(cache_mode):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Valid cache modes
|
|
||||||
valid_modes = ["min", "max", "inline"]
|
|
||||||
if cache_mode.lower() not in valid_modes:
|
|
||||||
self.add_error(f"Invalid cache-mode: {cache_mode}. Must be one of: min, max, inline")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def validate_buildx_version(self, version: str) -> bool:
|
def validate_buildx_version(self, version: str) -> bool:
|
||||||
"""Validate buildx version.
|
"""Validate buildx version.
|
||||||
@@ -213,8 +172,6 @@ class CustomValidator(BaseValidator):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Basic version format validation
|
# Basic version format validation
|
||||||
import re
|
|
||||||
|
|
||||||
if not re.match(r"^v?\d+\.\d+(\.\d+)?$", version):
|
if not re.match(r"^v?\d+\.\d+(\.\d+)?$", version):
|
||||||
self.add_error(f"Invalid buildx-version format: {version}")
|
self.add_error(f"Invalid buildx-version format: {version}")
|
||||||
return False
|
return False
|
||||||
@@ -244,8 +201,6 @@ class CustomValidator(BaseValidator):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Docker Hub username rules: lowercase letters, digits, periods, hyphens, underscores
|
# Docker Hub username rules: lowercase letters, digits, periods, hyphens, underscores
|
||||||
import re
|
|
||||||
|
|
||||||
if not re.match(r"^[a-z0-9._-]+$", username.lower()):
|
if not re.match(r"^[a-z0-9._-]+$", username.lower()):
|
||||||
self.add_error(f"Invalid Docker Hub username format: {username}")
|
self.add_error(f"Invalid Docker Hub username format: {username}")
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ runs:
|
|||||||
dockerhub|github|both)
|
dockerhub|github|both)
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "::error::Invalid registry value. Must be 'dockerhub', 'github', or 'both'"
|
printf '%s\n' "::error::Invalid registry value. Must be 'dockerhub', 'github', or 'both'"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -120,7 +120,7 @@ runs:
|
|||||||
# Validate Docker Hub credentials if needed
|
# Validate Docker Hub credentials if needed
|
||||||
if [ "$INPUT_REGISTRY" = "dockerhub" ] || [ "$INPUT_REGISTRY" = "both" ]; then
|
if [ "$INPUT_REGISTRY" = "dockerhub" ] || [ "$INPUT_REGISTRY" = "both" ]; then
|
||||||
if [ -z "$INPUT_DOCKERHUB_USERNAME" ] || [ -z "$INPUT_DOCKERHUB_TOKEN" ]; then
|
if [ -z "$INPUT_DOCKERHUB_USERNAME" ] || [ -z "$INPUT_DOCKERHUB_TOKEN" ]; then
|
||||||
echo "::error::Docker Hub username and token are required when publishing to Docker Hub"
|
printf '%s\n' "::error::Docker Hub username and token are required when publishing to Docker Hub"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -129,49 +129,80 @@ runs:
|
|||||||
if [ "$INPUT_REGISTRY" = "github" ] || [ "$INPUT_REGISTRY" = "both" ]; then
|
if [ "$INPUT_REGISTRY" = "github" ] || [ "$INPUT_REGISTRY" = "both" ]; then
|
||||||
token="${INPUT_TOKEN:-${GITHUB_TOKEN:-}}"
|
token="${INPUT_TOKEN:-${GITHUB_TOKEN:-}}"
|
||||||
if [ -z "$token" ]; then
|
if [ -z "$token" ]; then
|
||||||
echo "::error::GitHub token is required when publishing to GitHub Packages"
|
printf '%s\n' "::error::GitHub token is required when publishing to GitHub Packages"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate context input for security
|
# Validate context input for security
|
||||||
INPUT_CONTEXT="${INPUT_CONTEXT:-.}"
|
INPUT_CONTEXT="${INPUT_CONTEXT:-.}"
|
||||||
|
|
||||||
case "$INPUT_CONTEXT" in
|
case "$INPUT_CONTEXT" in
|
||||||
.|./*|*/*)
|
.|./*|*/*)
|
||||||
# Relative paths are allowed
|
# Relative paths are allowed
|
||||||
|
# Check for path traversal attempts
|
||||||
|
case "$INPUT_CONTEXT" in
|
||||||
|
*/../*|../*|*/..)
|
||||||
|
printf '%s\n' "::error::Context path contains path traversal: '$INPUT_CONTEXT'"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
;;
|
;;
|
||||||
/*)
|
/*)
|
||||||
echo "::error::Context cannot be an absolute path: '$INPUT_CONTEXT'"
|
printf '%s\n' "::error::Context cannot be an absolute path: '$INPUT_CONTEXT'"
|
||||||
echo "::error::Use relative paths (e.g., '.', './app') to prevent code injection"
|
printf '%s\n' "::error::Use relative paths (e.g., '.', './app')"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
*://*)
|
git://*|git@*|https://*.git|https://github.com/*|https://gitlab.com/*)
|
||||||
echo "::warning::Context is a remote URL: '$INPUT_CONTEXT'"
|
# Allow trusted git repository URLs
|
||||||
echo "::warning::Ensure this URL is from a trusted source to prevent code injection"
|
printf '%s\n' "::notice::Using git repository URL for context"
|
||||||
|
;;
|
||||||
|
http://*|https://*)
|
||||||
|
printf '%s\n' "::error::Context cannot be an arbitrary HTTP URL: '$INPUT_CONTEXT'"
|
||||||
|
printf '%s\n' "::error::Only git repository URLs are allowed for remote contexts"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "::error::Invalid context format: '$INPUT_CONTEXT'"
|
||||||
|
printf '%s\n' "::error::Must be a relative path or git repository URL"
|
||||||
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Validate dockerfile input for security
|
# Validate dockerfile input for security
|
||||||
INPUT_DOCKERFILE="${INPUT_DOCKERFILE:-Dockerfile}"
|
INPUT_DOCKERFILE="${INPUT_DOCKERFILE:-Dockerfile}"
|
||||||
|
|
||||||
case "$INPUT_DOCKERFILE" in
|
case "$INPUT_DOCKERFILE" in
|
||||||
Dockerfile|*/Dockerfile|*.dockerfile|*/*.dockerfile)
|
Dockerfile|*/Dockerfile|*.dockerfile|*/*.dockerfile)
|
||||||
# Common dockerfile patterns are allowed
|
# Common dockerfile patterns are allowed
|
||||||
|
# Check for path traversal attempts
|
||||||
|
case "$INPUT_DOCKERFILE" in
|
||||||
|
*/../*|../*|*/..)
|
||||||
|
printf '%s\n' "::error::Dockerfile path contains path traversal: '$INPUT_DOCKERFILE'"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
;;
|
;;
|
||||||
/*)
|
/*)
|
||||||
echo "::error::Dockerfile path cannot be absolute: '$INPUT_DOCKERFILE'"
|
printf '%s\n' "::error::Dockerfile path cannot be absolute: '$INPUT_DOCKERFILE'"
|
||||||
echo "::error::Use relative paths (e.g., 'Dockerfile', './docker/Dockerfile')"
|
printf '%s\n' "::error::Use relative paths (e.g., 'Dockerfile', './docker/Dockerfile')"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
*://*)
|
*://*)
|
||||||
echo "::error::Dockerfile path cannot be a URL: '$INPUT_DOCKERFILE'"
|
printf '%s\n' "::error::Dockerfile path cannot be a URL: '$INPUT_DOCKERFILE'"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "::error::Invalid Dockerfile format: '$INPUT_DOCKERFILE'"
|
||||||
|
printf '%s\n' "::error::Must be 'Dockerfile', '*/Dockerfile', '*.dockerfile', or '*/*.dockerfile'"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "Input validation completed successfully"
|
printf '%s\n' "Input validation completed successfully"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||||
|
|
||||||
- name: Determine Image Names and Tags
|
- name: Determine Image Names and Tags
|
||||||
id: meta
|
id: meta
|
||||||
@@ -223,25 +254,25 @@ runs:
|
|||||||
# Output results
|
# Output results
|
||||||
printf 'image-name=%s\n' "$base_name" >> "$GITHUB_OUTPUT"
|
printf 'image-name=%s\n' "$base_name" >> "$GITHUB_OUTPUT"
|
||||||
{
|
{
|
||||||
echo 'tags<<EOF'
|
printf '%s\n' 'tags<<EOF'
|
||||||
echo "$tags"
|
printf '%s\n' "$tags"
|
||||||
echo 'EOF'
|
printf '%s\n' 'EOF'
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
echo "Image name: $base_name"
|
printf 'Image name: %s\n' "$base_name"
|
||||||
echo "Tags:"
|
printf '%s\n' "Tags:"
|
||||||
echo "$tags"
|
printf '%s\n' "$tags"
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
if: inputs.registry == 'dockerhub' || inputs.registry == 'both'
|
if: inputs.registry == 'dockerhub' || inputs.registry == 'both'
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
username: ${{ inputs.dockerhub-username }}
|
username: ${{ inputs.dockerhub-username }}
|
||||||
password: ${{ inputs.dockerhub-token }}
|
password: ${{ inputs.dockerhub-token }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: inputs.registry == 'github' || inputs.registry == 'both'
|
if: inputs.registry == 'github' || inputs.registry == 'both'
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -249,7 +280,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and Push Docker Image
|
- name: Build and Push Docker Image
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
with:
|
with:
|
||||||
context: ${{ inputs.context }}
|
context: ${{ inputs.context }}
|
||||||
file: ${{ inputs.dockerfile }}
|
file: ${{ inputs.dockerfile }}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Validation rules for docker-publish action
|
# Validation rules for docker-publish action
|
||||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||||
# Schema version: 1.0
|
# Schema version: 1.0
|
||||||
# Coverage: 73% (8/11 inputs)
|
# Coverage: 100% (11/11 inputs)
|
||||||
#
|
#
|
||||||
# This file defines validation rules for the docker-publish GitHub Action.
|
# This file defines validation rules for the docker-publish GitHub Action.
|
||||||
# Rules are automatically applied by validate-inputs action when this
|
# Rules are automatically applied by validate-inputs action when this
|
||||||
@@ -27,25 +27,27 @@ optional_inputs:
|
|||||||
- tags
|
- tags
|
||||||
- token
|
- token
|
||||||
conventions:
|
conventions:
|
||||||
|
build-args: key_value_list
|
||||||
|
context: file_path
|
||||||
dockerfile: file_path
|
dockerfile: file_path
|
||||||
dockerhub-token: github_token
|
dockerhub-token: github_token
|
||||||
dockerhub-username: username
|
dockerhub-username: username
|
||||||
image-name: docker_image_name
|
image-name: docker_image_name
|
||||||
platforms: docker_architectures
|
platforms: docker_architectures
|
||||||
registry: registry
|
push: boolean
|
||||||
|
registry: registry_enum
|
||||||
tags: docker_tag
|
tags: docker_tag
|
||||||
token: github_token
|
token: github_token
|
||||||
overrides:
|
overrides:
|
||||||
platforms: null
|
|
||||||
registry: registry_enum
|
registry: registry_enum
|
||||||
statistics:
|
statistics:
|
||||||
total_inputs: 11
|
total_inputs: 11
|
||||||
validated_inputs: 8
|
validated_inputs: 11
|
||||||
skipped_inputs: 1
|
skipped_inputs: 0
|
||||||
coverage_percentage: 73
|
coverage_percentage: 100
|
||||||
validation_coverage: 73
|
validation_coverage: 100
|
||||||
auto_detected: true
|
auto_detected: true
|
||||||
manual_review_required: true
|
manual_review_required: false
|
||||||
quality_indicators:
|
quality_indicators:
|
||||||
has_required_inputs: false
|
has_required_inputs: false
|
||||||
has_token_validation: true
|
has_token_validation: true
|
||||||
|
|||||||
@@ -288,9 +288,9 @@ runs:
|
|||||||
echo "Detected package manager: $package_manager"
|
echo "Detected package manager: $package_manager"
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
shell: sh
|
shell: sh
|
||||||
@@ -319,13 +319,13 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
if: steps.detect-pm.outputs.package-manager == 'bun'
|
if: steps.detect-pm.outputs.package-manager == 'bun'
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: Cache Node Dependencies
|
- name: Cache Node Dependencies
|
||||||
id: cache
|
id: cache
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: ${{ runner.os }}-eslint-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
key: ${{ runner.os }}-eslint-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
||||||
@@ -457,7 +457,7 @@ runs:
|
|||||||
|
|
||||||
- name: Upload SARIF Report
|
- name: Upload SARIF Report
|
||||||
if: inputs.mode == 'check' && inputs.report-format == 'sarif' && always()
|
if: inputs.mode == 'check' && inputs.report-format == 'sarif' && always()
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ inputs.working-directory }}/eslint-results.sarif
|
sarif_file: ${{ inputs.working-directory }}/eslint-results.sarif
|
||||||
|
|
||||||
@@ -508,7 +508,7 @@ runs:
|
|||||||
|
|
||||||
- name: Commit and Push Fixes
|
- name: Commit and Push Fixes
|
||||||
if: inputs.mode == 'fix' && success()
|
if: inputs.mode == 'fix' && success()
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||||
with:
|
with:
|
||||||
commit_message: 'style: autofix ESLint violations'
|
commit_message: 'style: autofix ESLint violations'
|
||||||
commit_user_name: ${{ inputs.username }}
|
commit_user_name: ${{ inputs.username }}
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ conventions:
|
|||||||
token: github_token
|
token: github_token
|
||||||
username: username
|
username: username
|
||||||
working-directory: file_path
|
working-directory: file_path
|
||||||
overrides: {}
|
overrides:
|
||||||
|
mode: mode_enum
|
||||||
statistics:
|
statistics:
|
||||||
total_inputs: 14
|
total_inputs: 14
|
||||||
validated_inputs: 14
|
validated_inputs: 14
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ const CATEGORIES = {
|
|||||||
'compress-images': 'Repository',
|
'compress-images': 'Repository',
|
||||||
'codeql-analysis': 'Repository',
|
'codeql-analysis': 'Repository',
|
||||||
|
|
||||||
|
// Security
|
||||||
|
'security-scan': 'Security',
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
'validate-inputs': 'Validation',
|
'validate-inputs': 'Validation',
|
||||||
};
|
};
|
||||||
@@ -120,6 +123,7 @@ const CATEGORY_ICONS = {
|
|||||||
Build: '🏗️',
|
Build: '🏗️',
|
||||||
Publishing: '🚀',
|
Publishing: '🚀',
|
||||||
Repository: '📦',
|
Repository: '📦',
|
||||||
|
Security: '🛡️',
|
||||||
Validation: '✅',
|
Validation: '✅',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +236,7 @@ function generateCategoryTables(actions) {
|
|||||||
let output = '';
|
let output = '';
|
||||||
|
|
||||||
// Sort categories by priority
|
// Sort categories by priority
|
||||||
const categoryOrder = ['Setup', 'Utilities', 'Linting', 'Testing', 'Build', 'Publishing', 'Repository', 'Validation'];
|
const categoryOrder = ['Setup', 'Utilities', 'Linting', 'Testing', 'Build', 'Publishing', 'Repository', 'Security', 'Validation'];
|
||||||
|
|
||||||
for (const category of categoryOrder) {
|
for (const category of categoryOrder) {
|
||||||
if (!categories[category]) continue;
|
if (!categories[category]) continue;
|
||||||
|
|||||||
@@ -159,13 +159,13 @@ runs:
|
|||||||
echo "Final detected Go version: $detected_version" >&2
|
echo "Final detected Go version: $detected_version" >&2
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.detect-go-version.outputs.detected-version }}
|
go-version: ${{ steps.detect-go-version.outputs.detected-version }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Download Dependencies
|
- name: Download Dependencies
|
||||||
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3
|
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3.0.4
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: ${{ inputs.max-retries }}
|
max_attempts: ${{ inputs.max-retries }}
|
||||||
@@ -253,7 +253,7 @@ runs:
|
|||||||
|
|
||||||
- name: Upload Build Artifacts
|
- name: Upload Build Artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: go-build-artifacts
|
name: go-build-artifacts
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -37,105 +37,78 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate working-directory if provided
|
# Validate working-directory if provided
|
||||||
if inputs.get("working-directory"):
|
if inputs.get("working-directory"):
|
||||||
result = self.file_validator.validate_file_path(
|
valid &= self.validate_with(
|
||||||
inputs["working-directory"], "working-directory"
|
self.file_validator,
|
||||||
|
"validate_file_path",
|
||||||
|
inputs["working-directory"],
|
||||||
|
"working-directory",
|
||||||
)
|
)
|
||||||
for error in self.file_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate golangci-lint-version if provided
|
# Validate golangci-lint-version if provided
|
||||||
if inputs.get("golangci-lint-version"):
|
if inputs.get("golangci-lint-version"):
|
||||||
value = inputs["golangci-lint-version"]
|
value = inputs["golangci-lint-version"]
|
||||||
# Accept 'latest' or version format
|
|
||||||
if value != "latest" and not self.is_github_expression(value):
|
if value != "latest" and not self.is_github_expression(value):
|
||||||
result = self.version_validator.validate_semantic_version(
|
valid &= self.validate_with(
|
||||||
value, "golangci-lint-version"
|
self.version_validator,
|
||||||
|
"validate_semantic_version",
|
||||||
|
value,
|
||||||
|
"golangci-lint-version",
|
||||||
)
|
)
|
||||||
for error in self.version_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.version_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate go-version if provided
|
# Validate go-version if provided
|
||||||
if inputs.get("go-version"):
|
if inputs.get("go-version"):
|
||||||
value = inputs["go-version"]
|
value = inputs["go-version"]
|
||||||
# Accept 'stable', 'oldstable' or version format
|
|
||||||
if value not in ["stable", "oldstable"] and not self.is_github_expression(value):
|
if value not in ["stable", "oldstable"] and not self.is_github_expression(value):
|
||||||
result = self.version_validator.validate_go_version(value, "go-version")
|
valid &= self.validate_with(
|
||||||
for error in self.version_validator.errors:
|
self.version_validator, "validate_go_version", value, "go-version"
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.version_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate config-file if provided
|
# Validate config-file if provided
|
||||||
if inputs.get("config-file"):
|
if inputs.get("config-file"):
|
||||||
result = self.file_validator.validate_file_path(inputs["config-file"], "config-file")
|
valid &= self.validate_with(
|
||||||
for error in self.file_validator.errors:
|
self.file_validator, "validate_file_path", inputs["config-file"], "config-file"
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate timeout if provided
|
# Validate timeout if provided
|
||||||
if inputs.get("timeout"):
|
if inputs.get("timeout"):
|
||||||
value = inputs["timeout"]
|
value = inputs["timeout"]
|
||||||
# Validate timeout format (e.g., 5m, 1h, 30s)
|
if not self.is_github_expression(value) and not re.match(r"^\d+[smh]$", value):
|
||||||
if not self.is_github_expression(value):
|
self.add_error(
|
||||||
timeout_pattern = r"^\d+[smh]$"
|
f"Invalid timeout format: {value}. Expected format like '5m', '1h', '30s'"
|
||||||
if not re.match(timeout_pattern, value):
|
)
|
||||||
self.add_error(
|
valid = False
|
||||||
f"Invalid timeout format: {value}. Expected format like '5m', '1h', '30s'"
|
|
||||||
)
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate boolean inputs
|
# Validate boolean inputs
|
||||||
for field in ["cache", "fail-on-error", "only-new-issues", "disable-all"]:
|
for field in ["cache", "fail-on-error", "only-new-issues", "disable-all"]:
|
||||||
if inputs.get(field):
|
if inputs.get(field):
|
||||||
result = self.boolean_validator.validate_boolean(inputs[field], field)
|
valid &= self.validate_with(
|
||||||
for error in self.boolean_validator.errors:
|
self.boolean_validator, "validate_boolean", inputs[field], field
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate report-format
|
# Validate report-format
|
||||||
if inputs.get("report-format"):
|
if inputs.get("report-format"):
|
||||||
value = inputs["report-format"]
|
valid &= self.validate_enum(
|
||||||
valid_formats = ["json", "sarif", "github-actions", "colored-line-number", "tab"]
|
inputs["report-format"],
|
||||||
if value not in valid_formats and not self.is_github_expression(value):
|
"report-format",
|
||||||
self.add_error(
|
["json", "sarif", "github-actions", "colored-line-number", "tab"],
|
||||||
f"Invalid report format: {value}. Must be one of: {', '.join(valid_formats)}"
|
case_sensitive=True,
|
||||||
)
|
)
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate max-retries
|
# Validate max-retries
|
||||||
if inputs.get("max-retries"):
|
if inputs.get("max-retries"):
|
||||||
result = self.numeric_validator.validate_numeric_range(
|
valid &= self.validate_with(
|
||||||
inputs["max-retries"], min_val=1, max_val=10, name="max-retries"
|
self.numeric_validator,
|
||||||
|
"validate_numeric_range",
|
||||||
|
inputs["max-retries"],
|
||||||
|
min_val=1,
|
||||||
|
max_val=10,
|
||||||
|
name="max-retries",
|
||||||
)
|
)
|
||||||
for error in self.numeric_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.numeric_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate enable-linters and disable-linters
|
# Validate enable-linters and disable-linters
|
||||||
for field in ["enable-linters", "disable-linters"]:
|
for field in ["enable-linters", "disable-linters"]:
|
||||||
if inputs.get(field):
|
if inputs.get(field):
|
||||||
value = inputs[field]
|
value = inputs[field]
|
||||||
|
|
||||||
# First check format - must be comma-separated without spaces
|
|
||||||
if not self.is_github_expression(value):
|
if not self.is_github_expression(value):
|
||||||
if " " in value:
|
if " " in value:
|
||||||
self.add_error(f"Invalid {field} format: spaces not allowed in linter list")
|
self.add_error(f"Invalid {field} format: spaces not allowed in linter list")
|
||||||
@@ -145,15 +118,9 @@ class CustomValidator(BaseValidator):
|
|||||||
f"Invalid {field} format: must be comma-separated list of linters"
|
f"Invalid {field} format: must be comma-separated list of linters"
|
||||||
)
|
)
|
||||||
valid = False
|
valid = False
|
||||||
|
valid &= self.validate_with(
|
||||||
# Then check for injection
|
self.security_validator, "validate_no_injection", value, field
|
||||||
result = self.security_validator.validate_no_injection(value, field)
|
)
|
||||||
for error in self.security_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.security_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ runs:
|
|||||||
validate_linter_list "$DISABLE_LINTERS" "disable-linters"
|
validate_linter_list "$DISABLE_LINTERS" "disable-linters"
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ inputs.go-version }}
|
go-version: ${{ inputs.go-version }}
|
||||||
cache: true
|
cache: true
|
||||||
@@ -218,7 +218,7 @@ runs:
|
|||||||
- name: Cache golangci-lint
|
- name: Cache golangci-lint
|
||||||
id: cache
|
id: cache
|
||||||
if: inputs.cache == 'true'
|
if: inputs.cache == 'true'
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/golangci-lint
|
~/.cache/golangci-lint
|
||||||
@@ -414,7 +414,7 @@ runs:
|
|||||||
|
|
||||||
- name: Upload Lint Results
|
- name: Upload Lint Results
|
||||||
if: always() && inputs.report-format == 'sarif'
|
if: always() && inputs.report-format == 'sarif'
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif
|
sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif
|
||||||
category: golangci-lint
|
category: golangci-lint
|
||||||
|
|||||||
@@ -36,15 +36,17 @@ conventions:
|
|||||||
disable-linters: linter_list
|
disable-linters: linter_list
|
||||||
enable-linters: linter_list
|
enable-linters: linter_list
|
||||||
fail-on-error: boolean
|
fail-on-error: boolean
|
||||||
go-version: semantic_version
|
go-version: go_version
|
||||||
golangci-lint-version: semantic_version
|
golangci-lint-version: semantic_version
|
||||||
max-retries: numeric_range_1_10
|
max-retries: numeric_range_1_10
|
||||||
only-new-issues: branch_name
|
only-new-issues: boolean
|
||||||
report-format: report_format
|
report-format: report_format
|
||||||
timeout: numeric_range_1_3600
|
timeout: timeout_with_unit
|
||||||
token: github_token
|
token: github_token
|
||||||
working-directory: file_path
|
working-directory: file_path
|
||||||
overrides:
|
overrides:
|
||||||
|
disable-linters: linter_list
|
||||||
|
enable-linters: linter_list
|
||||||
go-version: go_version
|
go-version: go_version
|
||||||
only-new-issues: boolean
|
only-new-issues: boolean
|
||||||
timeout: timeout_with_unit
|
timeout: timeout_with_unit
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Validation rules for language-version-detect action
|
# Validation rules for language-version-detect action
|
||||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||||
# Schema version: 1.0
|
# Schema version: 1.0
|
||||||
# Coverage: 67% (2/3 inputs)
|
# Coverage: 100% (3/3 inputs)
|
||||||
#
|
#
|
||||||
# This file defines validation rules for the language-version-detect GitHub Action.
|
# This file defines validation rules for the language-version-detect GitHub Action.
|
||||||
# Rules are automatically applied by validate-inputs action when this
|
# Rules are automatically applied by validate-inputs action when this
|
||||||
@@ -21,16 +21,17 @@ optional_inputs:
|
|||||||
- token
|
- token
|
||||||
conventions:
|
conventions:
|
||||||
default-version: semantic_version
|
default-version: semantic_version
|
||||||
|
language: language_enum
|
||||||
token: github_token
|
token: github_token
|
||||||
overrides: {}
|
overrides: {}
|
||||||
statistics:
|
statistics:
|
||||||
total_inputs: 3
|
total_inputs: 3
|
||||||
validated_inputs: 2
|
validated_inputs: 3
|
||||||
skipped_inputs: 0
|
skipped_inputs: 0
|
||||||
coverage_percentage: 67
|
coverage_percentage: 100
|
||||||
validation_coverage: 67
|
validation_coverage: 100
|
||||||
auto_detected: true
|
auto_detected: true
|
||||||
manual_review_required: true
|
manual_review_required: false
|
||||||
quality_indicators:
|
quality_indicators:
|
||||||
has_required_inputs: true
|
has_required_inputs: true
|
||||||
has_token_validation: true
|
has_token_validation: true
|
||||||
|
|||||||
@@ -42,109 +42,40 @@ class CustomValidator(BaseValidator):
|
|||||||
self.add_error("Input 'npm_token' is required")
|
self.add_error("Input 'npm_token' is required")
|
||||||
valid = False
|
valid = False
|
||||||
elif inputs["npm_token"]:
|
elif inputs["npm_token"]:
|
||||||
token = inputs["npm_token"]
|
valid &= self._validate_npm_token(inputs["npm_token"])
|
||||||
# Check for NPM classic token format first
|
|
||||||
if token.startswith("npm_"):
|
|
||||||
# NPM classic token format: npm_ followed by 36+ alphanumeric characters
|
|
||||||
if not re.match(r"^npm_[a-zA-Z0-9]{36,}$", token):
|
|
||||||
self.add_error("Invalid NPM token format")
|
|
||||||
valid = False
|
|
||||||
# Also check for injection
|
|
||||||
result = self.security_validator.validate_no_injection(token, "npm_token")
|
|
||||||
for error in self.security_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.security_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
else:
|
|
||||||
# Otherwise validate as GitHub token
|
|
||||||
result = self.token_validator.validate_github_token(token, required=True)
|
|
||||||
for error in self.token_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate registry-url
|
# Validate registry-url
|
||||||
if inputs.get("registry-url"):
|
if inputs.get("registry-url"):
|
||||||
url = inputs["registry-url"]
|
valid &= self._validate_registry_url(inputs["registry-url"])
|
||||||
if not self.is_github_expression(url):
|
|
||||||
# Must be http or https URL
|
|
||||||
if not url.startswith(("http://", "https://")):
|
|
||||||
self.add_error("Registry URL must use http or https protocol")
|
|
||||||
valid = False
|
|
||||||
else:
|
|
||||||
# Validate URL format
|
|
||||||
result = self.network_validator.validate_url(url, "registry-url")
|
|
||||||
for error in self.network_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.network_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate scope
|
# Validate scope
|
||||||
if inputs.get("scope"):
|
if inputs.get("scope"):
|
||||||
scope = inputs["scope"]
|
valid &= self._validate_scope(inputs["scope"])
|
||||||
if not self.is_github_expression(scope):
|
|
||||||
# Scope must start with @ and contain only valid characters
|
|
||||||
if not scope.startswith("@"):
|
|
||||||
self.add_error("Scope must start with @ symbol")
|
|
||||||
valid = False
|
|
||||||
elif not re.match(r"^@[a-z0-9][a-z0-9\-_.]*$", scope):
|
|
||||||
self.add_error(
|
|
||||||
"Invalid scope format: must be @org-name with lowercase "
|
|
||||||
"letters, numbers, hyphens, dots, and underscores"
|
|
||||||
)
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Check for injection
|
|
||||||
result = self.security_validator.validate_no_injection(scope, "scope")
|
|
||||||
for error in self.security_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.security_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate access
|
# Validate access
|
||||||
if inputs.get("access"):
|
if inputs.get("access"):
|
||||||
access = inputs["access"]
|
valid &= self.validate_enum(
|
||||||
if not self.is_github_expression(access):
|
inputs["access"], "access", ["public", "restricted", "private"]
|
||||||
valid_access = ["public", "restricted", "private"]
|
)
|
||||||
if access and access not in valid_access:
|
|
||||||
self.add_error(
|
|
||||||
f"Invalid access level: {access}. Must be one of: {', '.join(valid_access)}"
|
|
||||||
)
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate boolean inputs (only always-auth and include-merged-tags are strict)
|
# Validate boolean inputs (only always-auth and include-merged-tags are strict)
|
||||||
for field in ["always-auth", "include-merged-tags"]:
|
for field in ["always-auth", "include-merged-tags"]:
|
||||||
if inputs.get(field):
|
if inputs.get(field):
|
||||||
result = self.boolean_validator.validate_boolean(inputs[field], field)
|
valid &= self.validate_with(
|
||||||
for error in self.boolean_validator.errors:
|
self.boolean_validator, "validate_boolean", inputs[field], field
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# provenance and dry-run accept any value (npm handles them)
|
# provenance and dry-run accept any value (npm handles them)
|
||||||
# No validation needed for these
|
# No validation needed for these
|
||||||
|
|
||||||
# Validate package-version
|
# Validate package-version
|
||||||
if inputs.get("package-version"):
|
if inputs.get("package-version"):
|
||||||
result = self.version_validator.validate_semantic_version(
|
valid &= self.validate_with(
|
||||||
inputs["package-version"], "package-version"
|
self.version_validator,
|
||||||
|
"validate_semantic_version",
|
||||||
|
inputs["package-version"],
|
||||||
|
"package-version",
|
||||||
)
|
)
|
||||||
for error in self.version_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.version_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate tag
|
# Validate tag
|
||||||
if inputs.get("tag"):
|
if inputs.get("tag"):
|
||||||
@@ -161,16 +92,57 @@ class CustomValidator(BaseValidator):
|
|||||||
# Validate working-directory and ignore-scripts as file paths
|
# Validate working-directory and ignore-scripts as file paths
|
||||||
for field in ["working-directory", "ignore-scripts"]:
|
for field in ["working-directory", "ignore-scripts"]:
|
||||||
if inputs.get(field):
|
if inputs.get(field):
|
||||||
result = self.file_validator.validate_path(inputs[field], field)
|
valid &= self.validate_with(
|
||||||
for error in self.file_validator.errors:
|
self.file_validator, "validate_path", inputs[field], field
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
def _validate_npm_token(self, token: str) -> bool:
|
||||||
|
"""Validate NPM token format."""
|
||||||
|
# Check for NPM classic token format first
|
||||||
|
if token.startswith("npm_"):
|
||||||
|
# NPM classic token format: npm_ followed by 36+ alphanumeric characters
|
||||||
|
if not re.match(r"^npm_[a-zA-Z0-9]{36,}$", token):
|
||||||
|
self.add_error("Invalid NPM token format")
|
||||||
|
return False
|
||||||
|
# Also check for injection
|
||||||
|
return self.validate_with(
|
||||||
|
self.security_validator, "validate_no_injection", token, "npm_token"
|
||||||
|
)
|
||||||
|
# Otherwise validate as GitHub token
|
||||||
|
return self.validate_with(
|
||||||
|
self.token_validator, "validate_github_token", token, required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_registry_url(self, url: str) -> bool:
|
||||||
|
"""Validate registry URL format."""
|
||||||
|
if self.is_github_expression(url):
|
||||||
|
return True
|
||||||
|
# Must be http or https URL
|
||||||
|
if not url.startswith(("http://", "https://")):
|
||||||
|
self.add_error("Registry URL must use http or https protocol")
|
||||||
|
return False
|
||||||
|
# Validate URL format
|
||||||
|
return self.validate_with(self.network_validator, "validate_url", url, "registry-url")
|
||||||
|
|
||||||
|
def _validate_scope(self, scope: str) -> bool:
|
||||||
|
"""Validate NPM scope format."""
|
||||||
|
if self.is_github_expression(scope):
|
||||||
|
return True
|
||||||
|
# Scope must start with @ and contain only valid characters
|
||||||
|
if not scope.startswith("@"):
|
||||||
|
self.add_error("Scope must start with @ symbol")
|
||||||
|
return False
|
||||||
|
if not re.match(r"^@[a-z0-9][a-z0-9\-_.]*$", scope):
|
||||||
|
self.add_error(
|
||||||
|
"Invalid scope format: must be @org-name with lowercase "
|
||||||
|
"letters, numbers, hyphens, dots, and underscores"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
# Check for injection
|
||||||
|
return self.validate_with(self.security_validator, "validate_no_injection", scope, "scope")
|
||||||
|
|
||||||
def get_required_inputs(self) -> list[str]:
|
def get_required_inputs(self) -> list[str]:
|
||||||
"""Get list of required inputs."""
|
"""Get list of required inputs."""
|
||||||
return ["npm_token"]
|
return ["npm_token"]
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ runs:
|
|||||||
echo "Detected package manager: $package_manager"
|
echo "Detected package manager: $package_manager"
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
shell: sh
|
shell: sh
|
||||||
@@ -152,13 +152,13 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
if: steps.detect-pm.outputs.package-manager == 'bun'
|
if: steps.detect-pm.outputs.package-manager == 'bun'
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: Cache Node Dependencies
|
- name: Cache Node Dependencies
|
||||||
id: cache
|
id: cache
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: ${{ runner.os }}-npm-publish-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
key: ${{ runner.os }}-npm-publish-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ optional_inputs:
|
|||||||
- token
|
- token
|
||||||
conventions:
|
conventions:
|
||||||
npm_token: github_token
|
npm_token: github_token
|
||||||
package-version: semantic_version
|
package-version: strict_semantic_version
|
||||||
registry-url: url
|
registry-url: url
|
||||||
scope: scope
|
scope: scope
|
||||||
token: github_token
|
token: github_token
|
||||||
|
|||||||
681
package-lock.json
generated
681
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@
|
|||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"markdown-table": "^3.0.3",
|
"markdown-table": "^3.0.3",
|
||||||
"markdown-table-formatter": "^1.6.0",
|
"markdown-table-formatter": "^1.6.0",
|
||||||
"markdownlint-cli2": "^0.19.0",
|
"markdownlint-cli2": "^0.20.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"yaml-lint": "^1.7.0"
|
"yaml-lint": "^1.7.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,59 +33,31 @@ class CustomValidator(BaseValidator):
|
|||||||
# Validate token (optional)
|
# Validate token (optional)
|
||||||
if inputs.get("token"):
|
if inputs.get("token"):
|
||||||
token = inputs["token"]
|
token = inputs["token"]
|
||||||
result = self.token_validator.validate_github_token(token)
|
valid &= self.validate_with(self.token_validator, "validate_github_token", token)
|
||||||
for error in self.token_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Also check for variable expansion
|
# Also check for variable expansion
|
||||||
if not self.is_github_expression(token):
|
if not self.is_github_expression(token):
|
||||||
result = self.security_validator.validate_no_injection(token, "token")
|
valid &= self.validate_with(
|
||||||
for error in self.security_validator.errors:
|
self.security_validator, "validate_no_injection", token, "token"
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.security_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate email (optional, empty means use default)
|
# Validate email (optional, empty means use default)
|
||||||
if "email" in inputs and inputs["email"] and inputs["email"] != "":
|
if inputs.get("email"):
|
||||||
email = inputs["email"]
|
email = inputs["email"]
|
||||||
result = self.network_validator.validate_email(email, "email")
|
valid &= self.validate_with(self.network_validator, "validate_email", email, "email")
|
||||||
for error in self.network_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.network_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Also check for shell metacharacters (but allow @ and .)
|
# Also check for shell metacharacters (but allow @ and .)
|
||||||
if not self.is_github_expression(email):
|
if not self.is_github_expression(email):
|
||||||
# Only check for dangerous shell metacharacters, not @ or .
|
|
||||||
dangerous_chars = [";", "&", "|", "`", "$", "(", ")", "<", ">", "\n", "\r"]
|
dangerous_chars = [";", "&", "|", "`", "$", "(", ")", "<", ">", "\n", "\r"]
|
||||||
for char in dangerous_chars:
|
if any(char in email for char in dangerous_chars):
|
||||||
if char in email:
|
self.add_error("email: Contains dangerous shell metacharacter")
|
||||||
self.add_error(f"email: Contains dangerous character '{char}'")
|
valid = False
|
||||||
valid = False
|
|
||||||
break
|
|
||||||
|
|
||||||
# Validate username (optional)
|
# Validate username (optional)
|
||||||
if inputs.get("username"):
|
if inputs.get("username"):
|
||||||
username = inputs["username"]
|
username = inputs["username"]
|
||||||
if not self.is_github_expression(username):
|
if not self.is_github_expression(username):
|
||||||
# Check for injection
|
valid &= self.validate_with(
|
||||||
result = self.security_validator.validate_no_injection(username, "username")
|
self.security_validator, "validate_no_injection", username, "username"
|
||||||
for error in self.security_validator.errors:
|
)
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.security_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Check username length (GitHub usernames are max 39 characters)
|
|
||||||
if len(username) > 39:
|
if len(username) > 39:
|
||||||
self.add_error("Username is too long (max 39 characters)")
|
self.add_error("Username is too long (max 39 characters)")
|
||||||
valid = False
|
valid = False
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ runs:
|
|||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
id: setup-php
|
id: setup-php
|
||||||
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
|
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ steps.detect-php-version.outputs.detected-version }}
|
php-version: ${{ steps.detect-php-version.outputs.detected-version }}
|
||||||
extensions: ${{ inputs.extensions }}
|
extensions: ${{ inputs.extensions }}
|
||||||
@@ -356,7 +356,7 @@ runs:
|
|||||||
|
|
||||||
- name: Cache Composer packages
|
- name: Cache Composer packages
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
vendor
|
vendor
|
||||||
@@ -376,7 +376,7 @@ runs:
|
|||||||
composer clear-cache
|
composer clear-cache
|
||||||
|
|
||||||
- name: Install Composer Dependencies
|
- name: Install Composer Dependencies
|
||||||
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3
|
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3.0.4
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: ${{ inputs.max-retries }}
|
max_attempts: ${{ inputs.max-retries }}
|
||||||
@@ -454,7 +454,7 @@ runs:
|
|||||||
phpunit_output=$(composer test 2>&1) || phpunit_exit_code=$?
|
phpunit_output=$(composer test 2>&1) || phpunit_exit_code=$?
|
||||||
elif [ -f "vendor/bin/phpunit" ]; then
|
elif [ -f "vendor/bin/phpunit" ]; then
|
||||||
echo "Running PHPUnit directly..."
|
echo "Running PHPUnit directly..."
|
||||||
phpunit_output=$(vendor/bin/phpunit --verbose 2>&1) || phpunit_exit_code=$?
|
phpunit_output=$(vendor/bin/phpunit 2>&1) || phpunit_exit_code=$?
|
||||||
else
|
else
|
||||||
echo "::error::PHPUnit not found. Ensure Composer dependencies are installed."
|
echo "::error::PHPUnit not found. Ensure Composer dependencies are installed."
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Validation rules for php-tests action
|
# Validation rules for php-tests action
|
||||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||||
# Schema version: 1.0
|
# Schema version: 1.0
|
||||||
# Coverage: 78% (7/9 inputs)
|
# Coverage: 89% (8/9 inputs)
|
||||||
#
|
#
|
||||||
# This file defines validation rules for the php-tests GitHub Action.
|
# This file defines validation rules for the php-tests GitHub Action.
|
||||||
# Rules are automatically applied by validate-inputs action when this
|
# Rules are automatically applied by validate-inputs action when this
|
||||||
@@ -27,7 +27,8 @@ optional_inputs:
|
|||||||
conventions:
|
conventions:
|
||||||
coverage: coverage_driver
|
coverage: coverage_driver
|
||||||
email: email
|
email: email
|
||||||
framework: boolean
|
extensions: php_extensions
|
||||||
|
framework: framework_mode
|
||||||
max-retries: numeric_range_1_10
|
max-retries: numeric_range_1_10
|
||||||
php-version: semantic_version
|
php-version: semantic_version
|
||||||
token: github_token
|
token: github_token
|
||||||
@@ -35,12 +36,12 @@ conventions:
|
|||||||
overrides: {}
|
overrides: {}
|
||||||
statistics:
|
statistics:
|
||||||
total_inputs: 9
|
total_inputs: 9
|
||||||
validated_inputs: 7
|
validated_inputs: 8
|
||||||
skipped_inputs: 0
|
skipped_inputs: 0
|
||||||
coverage_percentage: 78
|
coverage_percentage: 89
|
||||||
validation_coverage: 78
|
validation_coverage: 89
|
||||||
auto_detected: true
|
auto_detected: true
|
||||||
manual_review_required: true
|
manual_review_required: false
|
||||||
quality_indicators:
|
quality_indicators:
|
||||||
has_required_inputs: false
|
has_required_inputs: false
|
||||||
has_token_validation: true
|
has_token_validation: true
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Validate Inputs
|
- name: Validate Inputs
|
||||||
id: validate
|
id: validate
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: pr-lint
|
action-type: pr-lint
|
||||||
token: ${{ inputs.token }}
|
token: ${{ inputs.token }}
|
||||||
@@ -54,13 +54,9 @@ runs:
|
|||||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||||
with:
|
with:
|
||||||
token: ${{ inputs.token || github.token }}
|
token: ${{ inputs.token || github.token }}
|
||||||
ref: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref_name }}
|
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
# If you use VALIDATE_ALL_CODEBASE = true, you can remove this line to
|
|
||||||
# improve performance
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
# ╭──────────────────────────────────────────────────────────╮
|
# ╭──────────────────────────────────────────────────────────╮
|
||||||
# │ Install packages for linting │
|
# │ Install packages for linting │
|
||||||
# ╰──────────────────────────────────────────────────────────╯
|
# ╰──────────────────────────────────────────────────────────╯
|
||||||
@@ -74,6 +70,29 @@ runs:
|
|||||||
|
|
||||||
if [ -f package.json ]; then
|
if [ -f package.json ]; then
|
||||||
printf '%s\n' "found=true" >> "$GITHUB_OUTPUT"
|
printf '%s\n' "found=true" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Check if packageManager field is set (for corepack)
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
has_package_manager=$(jq -r '.packageManager // empty' package.json 2>/dev/null || printf '')
|
||||||
|
if [ -n "$has_package_manager" ]; then
|
||||||
|
printf '%s\n' "has-package-manager=true" >> "$GITHUB_OUTPUT"
|
||||||
|
printf 'Found packageManager field: %s\n' "$has_package_manager"
|
||||||
|
else
|
||||||
|
printf '%s\n' "has-package-manager=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Fallback: check with grep if jq not available
|
||||||
|
# Use robust pattern to verify non-empty value
|
||||||
|
if grep -q '"packageManager"[[:space:]]*:[[:space:]]*"[^"]\+"' package.json 2>/dev/null; then
|
||||||
|
printf '%s\n' "has-package-manager=true" >> "$GITHUB_OUTPUT"
|
||||||
|
printf '%s\n' "Found packageManager field in package.json"
|
||||||
|
else
|
||||||
|
printf '%s\n' "has-package-manager=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Explicitly set has-package-manager to false when package.json doesn't exist
|
||||||
|
printf '%s\n' "has-package-manager=false" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Detect Package Manager
|
- name: Detect Package Manager
|
||||||
@@ -95,34 +114,39 @@ runs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
printf 'package-manager=%s\n' "$package_manager" >> "$GITHUB_OUTPUT"
|
printf 'package-manager=%s\n' "$package_manager" >> "$GITHUB_OUTPUT"
|
||||||
echo "Detected package manager: $package_manager"
|
printf 'Detected package manager: %s\n' "$package_manager"
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
if: steps.detect-node.outputs.found == 'true'
|
if: steps.detect-node.outputs.found == 'true'
|
||||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
if: steps.detect-node.outputs.found == 'true'
|
if: steps.detect-node.outputs.found == 'true' && steps.detect-node.outputs.has-package-manager == 'true'
|
||||||
shell: sh
|
shell: sh
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
corepack enable
|
corepack enable
|
||||||
|
printf '%s\n' "Corepack enabled - package manager will be installed automatically from package.json"
|
||||||
|
|
||||||
- name: Install Package Manager
|
- name: Install Package Manager (Fallback)
|
||||||
if: steps.detect-node.outputs.found == 'true'
|
if: steps.detect-node.outputs.found == 'true' && steps.detect-node.outputs.has-package-manager == 'false'
|
||||||
shell: sh
|
shell: sh
|
||||||
env:
|
env:
|
||||||
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
|
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
|
printf 'No packageManager field found, using detected package manager: %s\n' "$PACKAGE_MANAGER"
|
||||||
|
|
||||||
case "$PACKAGE_MANAGER" in
|
case "$PACKAGE_MANAGER" in
|
||||||
pnpm)
|
pnpm)
|
||||||
|
corepack enable
|
||||||
corepack prepare pnpm@latest --activate
|
corepack prepare pnpm@latest --activate
|
||||||
;;
|
;;
|
||||||
yarn)
|
yarn)
|
||||||
|
corepack enable
|
||||||
corepack prepare yarn@stable --activate
|
corepack prepare yarn@stable --activate
|
||||||
;;
|
;;
|
||||||
bun|npm)
|
bun|npm)
|
||||||
@@ -132,14 +156,14 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
if: steps.detect-node.outputs.found == 'true' && steps.detect-pm.outputs.package-manager == 'bun'
|
if: steps.detect-node.outputs.found == 'true' && steps.detect-pm.outputs.package-manager == 'bun'
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: Cache Node Dependencies
|
- name: Cache Node Dependencies
|
||||||
if: steps.detect-node.outputs.found == 'true'
|
if: steps.detect-node.outputs.found == 'true'
|
||||||
id: node-cache
|
id: node-cache
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: ${{ runner.os }}-pr-lint-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
key: ${{ runner.os }}-pr-lint-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
||||||
@@ -154,16 +178,21 @@ runs:
|
|||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
echo "Installing dependencies using $PACKAGE_MANAGER..."
|
printf 'Installing dependencies using %s...\n' "$PACKAGE_MANAGER"
|
||||||
|
|
||||||
case "$PACKAGE_MANAGER" in
|
case "$PACKAGE_MANAGER" in
|
||||||
"pnpm")
|
"pnpm")
|
||||||
pnpm install --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
;;
|
;;
|
||||||
"yarn")
|
"yarn")
|
||||||
if [ -f ".yarnrc.yml" ]; then
|
# Detect Yarn version by checking actual version output
|
||||||
|
# Yarn 2+ (Berry) uses --immutable, Yarn 1.x (Classic) uses --frozen-lockfile
|
||||||
|
yarn_version=$(yarn --version 2>/dev/null || printf '1.0.0')
|
||||||
|
if printf '%s' "$yarn_version" | grep -q '^[2-9]'; then
|
||||||
|
# Yarn 2+ (Berry) - use --immutable
|
||||||
yarn install --immutable
|
yarn install --immutable
|
||||||
else
|
else
|
||||||
|
# Yarn 1.x (Classic) - use --frozen-lockfile
|
||||||
yarn install --frozen-lockfile
|
yarn install --frozen-lockfile
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -175,7 +204,7 @@ runs:
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "✅ Dependencies installed successfully"
|
printf '✅ Dependencies installed successfully\n'
|
||||||
|
|
||||||
# PHP tests if composer.json exists
|
# PHP tests if composer.json exists
|
||||||
- name: Detect composer.json
|
- name: Detect composer.json
|
||||||
@@ -219,12 +248,12 @@ runs:
|
|||||||
|
|
||||||
# Parse .tool-versions file
|
# Parse .tool-versions file
|
||||||
if [ -f .tool-versions ]; then
|
if [ -f .tool-versions ]; then
|
||||||
echo "Checking .tool-versions for php..." >&2
|
printf 'Checking .tool-versions for php...\n' >&2
|
||||||
version=$(awk '/^php[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
|
version=$(awk '/^php[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found PHP version in .tool-versions: $version" >&2
|
printf 'Found PHP version in .tool-versions: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -232,13 +261,13 @@ runs:
|
|||||||
|
|
||||||
# Parse Dockerfile
|
# Parse Dockerfile
|
||||||
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
|
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
|
||||||
echo "Checking Dockerfile for php..." >&2
|
printf 'Checking Dockerfile for php...\n' >&2
|
||||||
version=$(grep -iF "FROM" Dockerfile | grep -F "php:" | head -1 | \
|
version=$(grep -iF "FROM" Dockerfile | grep -F "php:" | head -1 | \
|
||||||
sed -n -E "s/.*php:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
sed -n -E "s/.*php:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found PHP version in Dockerfile: $version" >&2
|
printf 'Found PHP version in Dockerfile: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -246,29 +275,29 @@ runs:
|
|||||||
|
|
||||||
# Parse devcontainer.json
|
# Parse devcontainer.json
|
||||||
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
|
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
|
||||||
echo "Checking devcontainer.json for php..." >&2
|
printf 'Checking devcontainer.json for php...\n' >&2
|
||||||
if command -v jq >/dev/null 2>&1; then
|
if command -v jq >/dev/null 2>&1; then
|
||||||
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*php:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*php:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found PHP version in devcontainer: $version" >&2
|
printf 'Found PHP version in devcontainer: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "jq not found; skipping devcontainer.json parsing" >&2
|
printf 'jq not found; skipping devcontainer.json parsing\n' >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Parse .php-version file
|
# Parse .php-version file
|
||||||
if [ -z "$detected_version" ] && [ -f .php-version ]; then
|
if [ -z "$detected_version" ] && [ -f .php-version ]; then
|
||||||
echo "Checking .php-version..." >&2
|
printf 'Checking .php-version...\n' >&2
|
||||||
version=$(tr -d '\r' < .php-version | head -1)
|
version=$(tr -d '\r' < .php-version | head -1)
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found PHP version in .php-version: $version" >&2
|
printf 'Found PHP version in .php-version: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -276,7 +305,7 @@ runs:
|
|||||||
|
|
||||||
# Parse composer.json
|
# Parse composer.json
|
||||||
if [ -z "$detected_version" ] && [ -f composer.json ]; then
|
if [ -z "$detected_version" ] && [ -f composer.json ]; then
|
||||||
echo "Checking composer.json..." >&2
|
printf 'Checking composer.json...\n' >&2
|
||||||
if command -v jq >/dev/null 2>&1; then
|
if command -v jq >/dev/null 2>&1; then
|
||||||
version=$(jq -r '.require.php // empty' composer.json 2>/dev/null | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p')
|
version=$(jq -r '.require.php // empty' composer.json 2>/dev/null | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p')
|
||||||
if [ -z "$version" ]; then
|
if [ -z "$version" ]; then
|
||||||
@@ -285,34 +314,34 @@ runs:
|
|||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found PHP version in composer.json: $version" >&2
|
printf 'Found PHP version in composer.json: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "jq not found; skipping composer.json parsing" >&2
|
printf 'jq not found; skipping composer.json parsing\n' >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use default version if nothing detected
|
# Use default version if nothing detected
|
||||||
if [ -z "$detected_version" ]; then
|
if [ -z "$detected_version" ]; then
|
||||||
detected_version="$DEFAULT_VERSION"
|
detected_version="$DEFAULT_VERSION"
|
||||||
echo "Using default PHP version: $detected_version" >&2
|
printf 'Using default PHP version: %s\n' "$detected_version" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set output
|
# Set output
|
||||||
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
|
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
|
||||||
echo "Final detected PHP version: $detected_version" >&2
|
printf 'Final detected PHP version: %s\n' "$detected_version" >&2
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
if: steps.detect-php.outputs.found == 'true'
|
if: steps.detect-php.outputs.found == 'true'
|
||||||
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
|
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ steps.php-version.outputs.detected-version }}
|
php-version: ${{ steps.php-version.outputs.detected-version }}
|
||||||
tools: composer
|
tools: composer
|
||||||
coverage: none
|
coverage: none
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ inputs.token }}
|
GITHUB_TOKEN: ${{ inputs.token || github.token }}
|
||||||
|
|
||||||
- name: Setup problem matchers for PHP
|
- name: Setup problem matchers for PHP
|
||||||
if: steps.detect-php.outputs.found == 'true'
|
if: steps.detect-php.outputs.found == 'true'
|
||||||
@@ -322,7 +351,8 @@ runs:
|
|||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
echo "::add-matcher::$RUNNER_TOOL_CACHE/php.json"
|
matcher_path=$(printf '%s' "$RUNNER_TOOL_CACHE/php.json" | tr -d '\n\r')
|
||||||
|
printf '%s\n' "::add-matcher::$matcher_path"
|
||||||
|
|
||||||
- name: Install PHP dependencies
|
- name: Install PHP dependencies
|
||||||
if: steps.detect-php.outputs.found == 'true'
|
if: steps.detect-php.outputs.found == 'true'
|
||||||
@@ -348,7 +378,7 @@ runs:
|
|||||||
id: python-version
|
id: python-version
|
||||||
shell: sh
|
shell: sh
|
||||||
env:
|
env:
|
||||||
DEFAULT_VERSION: '3.11'
|
DEFAULT_VERSION: '3.14'
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
@@ -374,12 +404,12 @@ runs:
|
|||||||
|
|
||||||
# Parse .tool-versions file
|
# Parse .tool-versions file
|
||||||
if [ -f .tool-versions ]; then
|
if [ -f .tool-versions ]; then
|
||||||
echo "Checking .tool-versions for python..." >&2
|
printf 'Checking .tool-versions for python...\n' >&2
|
||||||
version=$(awk '/^python[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
|
version=$(awk '/^python[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Python version in .tool-versions: $version" >&2
|
printf 'Found Python version in .tool-versions: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -387,13 +417,13 @@ runs:
|
|||||||
|
|
||||||
# Parse Dockerfile
|
# Parse Dockerfile
|
||||||
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
|
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
|
||||||
echo "Checking Dockerfile for python..." >&2
|
printf 'Checking Dockerfile for python...\n' >&2
|
||||||
version=$(grep -iF "FROM" Dockerfile | grep -F "python:" | head -1 | \
|
version=$(grep -iF "FROM" Dockerfile | grep -F "python:" | head -1 | \
|
||||||
sed -n -E "s/.*python:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
sed -n -E "s/.*python:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Python version in Dockerfile: $version" >&2
|
printf 'Found Python version in Dockerfile: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -401,29 +431,29 @@ runs:
|
|||||||
|
|
||||||
# Parse devcontainer.json
|
# Parse devcontainer.json
|
||||||
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
|
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
|
||||||
echo "Checking devcontainer.json for python..." >&2
|
printf 'Checking devcontainer.json for python...\n' >&2
|
||||||
if command -v jq >/dev/null 2>&1; then
|
if command -v jq >/dev/null 2>&1; then
|
||||||
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*python:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*python:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Python version in devcontainer: $version" >&2
|
printf 'Found Python version in devcontainer: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "jq not found; skipping devcontainer.json parsing" >&2
|
printf 'jq not found; skipping devcontainer.json parsing\n' >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Parse .python-version file
|
# Parse .python-version file
|
||||||
if [ -z "$detected_version" ] && [ -f .python-version ]; then
|
if [ -z "$detected_version" ] && [ -f .python-version ]; then
|
||||||
echo "Checking .python-version..." >&2
|
printf 'Checking .python-version...\n' >&2
|
||||||
version=$(tr -d '\r' < .python-version | head -1)
|
version=$(tr -d '\r' < .python-version | head -1)
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Python version in .python-version: $version" >&2
|
printf 'Found Python version in .python-version: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -431,13 +461,13 @@ runs:
|
|||||||
|
|
||||||
# Parse pyproject.toml
|
# Parse pyproject.toml
|
||||||
if [ -z "$detected_version" ] && [ -f pyproject.toml ]; then
|
if [ -z "$detected_version" ] && [ -f pyproject.toml ]; then
|
||||||
echo "Checking pyproject.toml..." >&2
|
printf 'Checking pyproject.toml...\n' >&2
|
||||||
if grep -q '^\\[project\\]' pyproject.toml; then
|
if grep -q '^\[project\]' pyproject.toml; then
|
||||||
version=$(grep -A 20 '^\\[project\\]' pyproject.toml | grep -E '^\\s*requires-python[[:space:]]*=' | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p' | head -1)
|
version=$(grep -A 20 '^\[project\]' pyproject.toml | grep -E '^\s*requires-python[[:space:]]*=' | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p' | head -1)
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Python version in pyproject.toml: $version" >&2
|
printf 'Found Python version in pyproject.toml: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -447,16 +477,16 @@ runs:
|
|||||||
# Use default version if nothing detected
|
# Use default version if nothing detected
|
||||||
if [ -z "$detected_version" ]; then
|
if [ -z "$detected_version" ]; then
|
||||||
detected_version="$DEFAULT_VERSION"
|
detected_version="$DEFAULT_VERSION"
|
||||||
echo "Using default Python version: $detected_version" >&2
|
printf 'Using default Python version: %s\n' "$detected_version" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set output
|
# Set output
|
||||||
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
|
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
|
||||||
echo "Final detected Python version: $detected_version" >&2
|
printf 'Final detected Python version: %s\n' "$detected_version" >&2
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
if: steps.detect-python.outputs.found == 'true'
|
if: steps.detect-python.outputs.found == 'true'
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ steps.python-version.outputs.detected-version }}
|
python-version: ${{ steps.python-version.outputs.detected-version }}
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
@@ -485,7 +515,7 @@ runs:
|
|||||||
id: go-version
|
id: go-version
|
||||||
shell: sh
|
shell: sh
|
||||||
env:
|
env:
|
||||||
DEFAULT_VERSION: '1.24'
|
DEFAULT_VERSION: '1.25'
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
@@ -511,12 +541,12 @@ runs:
|
|||||||
|
|
||||||
# Parse .tool-versions file
|
# Parse .tool-versions file
|
||||||
if [ -f .tool-versions ]; then
|
if [ -f .tool-versions ]; then
|
||||||
echo "Checking .tool-versions for golang..." >&2
|
printf 'Checking .tool-versions for golang...\n' >&2
|
||||||
version=$(awk '/^golang[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
|
version=$(awk '/^golang[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Go version in .tool-versions: $version" >&2
|
printf 'Found Go version in .tool-versions: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -524,13 +554,13 @@ runs:
|
|||||||
|
|
||||||
# Parse Dockerfile
|
# Parse Dockerfile
|
||||||
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
|
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
|
||||||
echo "Checking Dockerfile for golang..." >&2
|
printf 'Checking Dockerfile for golang...\n' >&2
|
||||||
version=$(grep -iF "FROM" Dockerfile | grep -F "golang:" | head -1 | \
|
version=$(grep -iF "FROM" Dockerfile | grep -F "golang:" | head -1 | \
|
||||||
sed -n -E "s/.*golang:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
sed -n -E "s/.*golang:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Go version in Dockerfile: $version" >&2
|
printf 'Found Go version in Dockerfile: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -538,29 +568,29 @@ runs:
|
|||||||
|
|
||||||
# Parse devcontainer.json
|
# Parse devcontainer.json
|
||||||
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
|
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
|
||||||
echo "Checking devcontainer.json for golang..." >&2
|
printf 'Checking devcontainer.json for golang...\n' >&2
|
||||||
if command -v jq >/dev/null 2>&1; then
|
if command -v jq >/dev/null 2>&1; then
|
||||||
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*golang:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*golang:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Go version in devcontainer: $version" >&2
|
printf 'Found Go version in devcontainer: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "jq not found; skipping devcontainer.json parsing" >&2
|
printf 'jq not found; skipping devcontainer.json parsing\n' >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Parse .go-version file
|
# Parse .go-version file
|
||||||
if [ -z "$detected_version" ] && [ -f .go-version ]; then
|
if [ -z "$detected_version" ] && [ -f .go-version ]; then
|
||||||
echo "Checking .go-version..." >&2
|
printf 'Checking .go-version...\n' >&2
|
||||||
version=$(tr -d '\r' < .go-version | head -1)
|
version=$(tr -d '\r' < .go-version | head -1)
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Go version in .go-version: $version" >&2
|
printf 'Found Go version in .go-version: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -568,12 +598,12 @@ runs:
|
|||||||
|
|
||||||
# Parse go.mod
|
# Parse go.mod
|
||||||
if [ -z "$detected_version" ] && [ -f go.mod ]; then
|
if [ -z "$detected_version" ] && [ -f go.mod ]; then
|
||||||
echo "Checking go.mod..." >&2
|
printf 'Checking go.mod...\n' >&2
|
||||||
version=$(grep -E '^go[[:space:]]+[0-9]' go.mod | awk '{print $2}' | head -1 || echo "")
|
version=$(grep -E '^go[[:space:]]+[0-9]' go.mod | awk '{print $2}' | head -1 || echo "")
|
||||||
if [ -n "$version" ]; then
|
if [ -n "$version" ]; then
|
||||||
version=$(clean_version "$version")
|
version=$(clean_version "$version")
|
||||||
if validate_version "$version"; then
|
if validate_version "$version"; then
|
||||||
echo "Found Go version in go.mod: $version" >&2
|
printf 'Found Go version in go.mod: %s\n' "$version" >&2
|
||||||
detected_version="$version"
|
detected_version="$version"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -582,16 +612,16 @@ runs:
|
|||||||
# Use default version if nothing detected
|
# Use default version if nothing detected
|
||||||
if [ -z "$detected_version" ]; then
|
if [ -z "$detected_version" ]; then
|
||||||
detected_version="$DEFAULT_VERSION"
|
detected_version="$DEFAULT_VERSION"
|
||||||
echo "Using default Go version: $detected_version" >&2
|
printf 'Using default Go version: %s\n' "$detected_version" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set output
|
# Set output
|
||||||
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
|
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
|
||||||
echo "Final detected Go version: $detected_version" >&2
|
printf 'Final detected Go version: %s\n' "$detected_version" >&2
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: steps.detect-go.outputs.found == 'true'
|
if: steps.detect-go.outputs.found == 'true'
|
||||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.go-version.outputs.detected-version }}
|
go-version: ${{ steps.go-version.outputs.detected-version }}
|
||||||
cache: true
|
cache: true
|
||||||
@@ -602,7 +632,7 @@ runs:
|
|||||||
- name: MegaLinter
|
- name: MegaLinter
|
||||||
# You can override MegaLinter flavor used to have faster performances
|
# You can override MegaLinter flavor used to have faster performances
|
||||||
# More info at https://megalinter.io/latest/flavors/
|
# More info at https://megalinter.io/latest/flavors/
|
||||||
uses: oxsecurity/megalinter/flavors/cupcake@62c799d895af9bcbca5eacfebca29d527f125a57 # v9.1.0
|
uses: oxsecurity/megalinter/flavors/cupcake@42bb470545e359597e7f12156947c436e4e3fb9a # v9.3.0
|
||||||
id: ml
|
id: ml
|
||||||
|
|
||||||
# All available variables are described in documentation
|
# All available variables are described in documentation
|
||||||
@@ -620,11 +650,7 @@ runs:
|
|||||||
# github.event_name == 'push' &&
|
# github.event_name == 'push' &&
|
||||||
# contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref)
|
# contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref)
|
||||||
# }}
|
# }}
|
||||||
VALIDATE_ALL_CODEBASE: >-
|
VALIDATE_ALL_CODEBASE: false
|
||||||
${{
|
|
||||||
github.event_name == 'push' &&
|
|
||||||
contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref)
|
|
||||||
}}
|
|
||||||
|
|
||||||
GITHUB_TOKEN: ${{ inputs.token || github.token }}
|
GITHUB_TOKEN: ${{ inputs.token || github.token }}
|
||||||
|
|
||||||
@@ -632,7 +658,7 @@ runs:
|
|||||||
#
|
#
|
||||||
# When active, APPLY_FIXES must also be defined as environment variable
|
# When active, APPLY_FIXES must also be defined as environment variable
|
||||||
# (in .github/workflows/mega-linter.yml or other CI tool)
|
# (in .github/workflows/mega-linter.yml or other CI tool)
|
||||||
APPLY_FIXES: all
|
APPLY_FIXES: none
|
||||||
|
|
||||||
# Decide which event triggers application of fixes in a commit or a PR
|
# Decide which event triggers application of fixes in a commit or a PR
|
||||||
# (pull_request, push, all)
|
# (pull_request, push, all)
|
||||||
@@ -648,124 +674,13 @@ runs:
|
|||||||
# Uncomment to disable copy-paste and spell checks
|
# Uncomment to disable copy-paste and spell checks
|
||||||
DISABLE: COPYPASTE,SPELL
|
DISABLE: COPYPASTE,SPELL
|
||||||
|
|
||||||
# Export env vars to make them available for subsequent expressions
|
|
||||||
- name: Export Apply Fixes Variables
|
|
||||||
shell: sh
|
|
||||||
run: |
|
|
||||||
echo "APPLY_FIXES_EVENT=pull_request" >> "$GITHUB_ENV"
|
|
||||||
echo "APPLY_FIXES_MODE=commit" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
# Upload MegaLinter artifacts
|
# Upload MegaLinter artifacts
|
||||||
- name: Archive production artifacts
|
- name: Archive production artifacts
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: MegaLinter reports
|
name: MegaLinter reports
|
||||||
include-hidden-files: 'true'
|
include-hidden-files: 'true'
|
||||||
path: |
|
path: |
|
||||||
megalinter-reports
|
megalinter-reports
|
||||||
mega-linter.log
|
mega-linter.log
|
||||||
|
|
||||||
# Set APPLY_FIXES_IF var for use in future steps
|
|
||||||
- name: Set APPLY_FIXES_IF var
|
|
||||||
shell: sh
|
|
||||||
env:
|
|
||||||
APPLY_FIXES_CONDITION: >-
|
|
||||||
${{
|
|
||||||
steps.ml.outputs.has_updated_sources == 1 &&
|
|
||||||
(env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) &&
|
|
||||||
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
|
|
||||||
}}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
# Sanitize by removing newlines to prevent env var injection
|
|
||||||
sanitized_condition="$(echo "$APPLY_FIXES_CONDITION" | tr -d '\n\r')"
|
|
||||||
printf 'APPLY_FIXES_IF=%s\n' "$sanitized_condition" >> "${GITHUB_ENV}"
|
|
||||||
|
|
||||||
# Set APPLY_FIXES_IF_* vars for use in future steps
|
|
||||||
- name: Set APPLY_FIXES_IF_* vars
|
|
||||||
shell: sh
|
|
||||||
env:
|
|
||||||
APPLY_FIXES_IF_PR_CONDITION: ${{ env.APPLY_FIXES_IF == 'true' && env.APPLY_FIXES_MODE == 'pull_request' }}
|
|
||||||
APPLY_FIXES_IF_COMMIT_CONDITION: ${{ env.APPLY_FIXES_IF == 'true' && env.APPLY_FIXES_MODE == 'commit' && (!contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref)) }}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
# Sanitize by removing newlines to prevent env var injection
|
|
||||||
sanitized_pr="$(echo "$APPLY_FIXES_IF_PR_CONDITION" | tr -d '\n\r')"
|
|
||||||
sanitized_commit="$(echo "$APPLY_FIXES_IF_COMMIT_CONDITION" | tr -d '\n\r')"
|
|
||||||
|
|
||||||
printf 'APPLY_FIXES_IF_PR=%s\n' "$sanitized_pr" >> "${GITHUB_ENV}"
|
|
||||||
printf 'APPLY_FIXES_IF_COMMIT=%s\n' "$sanitized_commit" >> "${GITHUB_ENV}"
|
|
||||||
|
|
||||||
# Create pull request if applicable
|
|
||||||
# (for now works only on PR from same repository, not from forks)
|
|
||||||
- name: Create Pull Request with applied fixes
|
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
|
||||||
id: cpr
|
|
||||||
if: env.APPLY_FIXES_IF_PR == 'true'
|
|
||||||
with:
|
|
||||||
token: ${{ inputs.token || github.token }}
|
|
||||||
commit-message: 'style: apply linter fixes'
|
|
||||||
title: 'style: apply linter fixes'
|
|
||||||
labels: bot
|
|
||||||
|
|
||||||
- name: Create PR output
|
|
||||||
if: env.APPLY_FIXES_IF_PR == 'true'
|
|
||||||
shell: sh
|
|
||||||
env:
|
|
||||||
PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }}
|
|
||||||
PR_URL: ${{ steps.cpr.outputs.pull-request-url }}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
echo "PR Number - $PR_NUMBER"
|
|
||||||
echo "PR URL - $PR_URL"
|
|
||||||
|
|
||||||
# Push new commit if applicable
|
|
||||||
# (for now works only on PR from same repository, not from forks)
|
|
||||||
- name: Prepare commit
|
|
||||||
if: env.APPLY_FIXES_IF_COMMIT == 'true'
|
|
||||||
shell: sh
|
|
||||||
env:
|
|
||||||
BRANCH_REF: >-
|
|
||||||
${{
|
|
||||||
github.event.pull_request.head.ref ||
|
|
||||||
github.head_ref ||
|
|
||||||
github.ref_name
|
|
||||||
}}
|
|
||||||
run: |
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
# Fix .git directory ownership after MegaLinter container execution
|
|
||||||
sudo chown -Rc "$UID" .git/
|
|
||||||
|
|
||||||
# Ensure we're on the correct branch (not in detached HEAD state)
|
|
||||||
# This is necessary because MegaLinter may leave the repo in a detached HEAD state
|
|
||||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
|
||||||
if [ "$current_branch" = "HEAD" ]; then
|
|
||||||
echo "Repository is in detached HEAD state, checking out $BRANCH_REF"
|
|
||||||
# Validate branch reference to prevent command injection
|
|
||||||
if ! git check-ref-format --branch "$BRANCH_REF"; then
|
|
||||||
echo "::error::Invalid branch reference format: $BRANCH_REF"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
git checkout "$BRANCH_REF"
|
|
||||||
else
|
|
||||||
echo "Repository is on branch: $current_branch"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Commit and push applied linter fixes
|
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
|
||||||
if: env.APPLY_FIXES_IF_COMMIT == 'true'
|
|
||||||
with:
|
|
||||||
branch: >-
|
|
||||||
${{
|
|
||||||
github.event.pull_request.head.ref ||
|
|
||||||
github.head_ref ||
|
|
||||||
github.ref
|
|
||||||
}}
|
|
||||||
commit_message: 'style: apply linter fixes'
|
|
||||||
commit_user_name: ${{ inputs.username }}
|
|
||||||
commit_user_email: ${{ inputs.email }}
|
|
||||||
|
|||||||
@@ -34,74 +34,45 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate pre-commit-config if provided
|
# Validate pre-commit-config if provided
|
||||||
if "pre-commit-config" in inputs:
|
if "pre-commit-config" in inputs:
|
||||||
result = self.file_validator.validate_file_path(
|
valid &= self.validate_with(
|
||||||
inputs["pre-commit-config"], "pre-commit-config"
|
self.file_validator,
|
||||||
|
"validate_file_path",
|
||||||
|
inputs["pre-commit-config"],
|
||||||
|
"pre-commit-config",
|
||||||
)
|
)
|
||||||
for error in self.file_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate base-branch if provided (just check for injection)
|
# Validate base-branch if provided (just check for injection)
|
||||||
if inputs.get("base-branch"):
|
if inputs.get("base-branch"):
|
||||||
# Check for dangerous characters that could cause shell injection
|
valid &= self.validate_with(
|
||||||
result = self.security_validator.validate_no_injection(
|
self.security_validator,
|
||||||
inputs["base-branch"], "base-branch"
|
"validate_no_injection",
|
||||||
|
inputs["base-branch"],
|
||||||
|
"base-branch",
|
||||||
)
|
)
|
||||||
for error in self.security_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.security_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate token if provided
|
# Validate token if provided
|
||||||
if inputs.get("token"):
|
if inputs.get("token"):
|
||||||
result = self.token_validator.validate_github_token(inputs["token"])
|
valid &= self.validate_with(
|
||||||
for error in self.token_validator.errors:
|
self.token_validator, "validate_github_token", inputs["token"]
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate commit_user if provided (allow spaces for Git usernames)
|
# Validate commit_user if provided (allow spaces for Git usernames)
|
||||||
# Check both underscore and hyphen versions since inputs can have either
|
commit_user_key = self.get_key_variant(inputs, "commit_user", "commit-user")
|
||||||
commit_user_key = (
|
|
||||||
"commit_user"
|
|
||||||
if "commit_user" in inputs
|
|
||||||
else "commit-user"
|
|
||||||
if "commit-user" in inputs
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
if commit_user_key and inputs[commit_user_key]:
|
if commit_user_key and inputs[commit_user_key]:
|
||||||
# Check for dangerous injection patterns
|
|
||||||
value = inputs[commit_user_key]
|
value = inputs[commit_user_key]
|
||||||
if any(char in value for char in [";", "&", "|", "`", "$", "(", ")", "\n", "\r"]):
|
if any(c in value for c in [";", "&", "|", "`", "$", "(", ")", "\n", "\r"]):
|
||||||
self.add_error(f"{commit_user_key}: Contains potentially dangerous characters")
|
self.add_error(f"{commit_user_key}: Contains potentially dangerous characters")
|
||||||
valid = False
|
valid = False
|
||||||
|
|
||||||
# Validate commit_email if provided
|
# Validate commit_email if provided
|
||||||
# Check both underscore and hyphen versions
|
commit_email_key = self.get_key_variant(inputs, "commit_email", "commit-email")
|
||||||
commit_email_key = (
|
|
||||||
"commit_email"
|
|
||||||
if "commit_email" in inputs
|
|
||||||
else "commit-email"
|
|
||||||
if "commit-email" in inputs
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
if commit_email_key and inputs[commit_email_key]:
|
if commit_email_key and inputs[commit_email_key]:
|
||||||
result = self.network_validator.validate_email(
|
valid &= self.validate_with(
|
||||||
inputs[commit_email_key], commit_email_key
|
self.network_validator,
|
||||||
|
"validate_email",
|
||||||
|
inputs[commit_email_key],
|
||||||
|
commit_email_key,
|
||||||
)
|
)
|
||||||
for error in self.network_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.network_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ runs:
|
|||||||
|
|
||||||
- name: Validate Inputs
|
- name: Validate Inputs
|
||||||
id: validate
|
id: validate
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: 'pre-commit'
|
action-type: 'pre-commit'
|
||||||
token: ${{ inputs.token }}
|
token: ${{ inputs.token }}
|
||||||
@@ -83,7 +83,7 @@ runs:
|
|||||||
- name: Push pre-commit fixes
|
- name: Push pre-commit fixes
|
||||||
id: push-fixes
|
id: push-fixes
|
||||||
if: always() # Push changes even when pre-commit fails
|
if: always() # Push changes even when pre-commit fails
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||||
with:
|
with:
|
||||||
commit_message: 'style(pre-commit): autofix'
|
commit_message: 'style(pre-commit): autofix'
|
||||||
commit_user_name: ${{ inputs.commit_user }}
|
commit_user_name: ${{ inputs.commit_user }}
|
||||||
|
|||||||
@@ -274,9 +274,9 @@ runs:
|
|||||||
echo "Detected package manager: $package_manager"
|
echo "Detected package manager: $package_manager"
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
shell: sh
|
shell: sh
|
||||||
@@ -305,13 +305,13 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
if: steps.detect-pm.outputs.package-manager == 'bun'
|
if: steps.detect-pm.outputs.package-manager == 'bun'
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: Cache Node Dependencies
|
- name: Cache Node Dependencies
|
||||||
id: cache
|
id: cache
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: ${{ runner.os }}-prettier-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
key: ${{ runner.os }}-prettier-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
||||||
@@ -468,7 +468,7 @@ runs:
|
|||||||
|
|
||||||
- name: Commit and Push Fixes
|
- name: Commit and Push Fixes
|
||||||
if: inputs.mode == 'fix' && success()
|
if: inputs.mode == 'fix' && success()
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||||
with:
|
with:
|
||||||
commit_message: 'style: autofix Prettier formatting'
|
commit_message: 'style: autofix Prettier formatting'
|
||||||
commit_user_name: ${{ inputs.username }}
|
commit_user_name: ${{ inputs.username }}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Validation rules for prettier-lint action
|
# Validation rules for prettier-lint action
|
||||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||||
# Schema version: 1.0
|
# Schema version: 1.0
|
||||||
# Coverage: 86% (12/14 inputs)
|
# Coverage: 100% (14/14 inputs)
|
||||||
#
|
#
|
||||||
# This file defines validation rules for the prettier-lint GitHub Action.
|
# This file defines validation rules for the prettier-lint GitHub Action.
|
||||||
# Rules are automatically applied by validate-inputs action when this
|
# Rules are automatically applied by validate-inputs action when this
|
||||||
@@ -34,21 +34,24 @@ conventions:
|
|||||||
config-file: file_path
|
config-file: file_path
|
||||||
email: email
|
email: email
|
||||||
fail-on-error: boolean
|
fail-on-error: boolean
|
||||||
|
file-pattern: path_list
|
||||||
ignore-file: file_path
|
ignore-file: file_path
|
||||||
max-retries: numeric_range_1_10
|
max-retries: numeric_range_1_10
|
||||||
mode: mode_enum
|
mode: mode_enum
|
||||||
|
plugins: linter_list
|
||||||
prettier-version: semantic_version
|
prettier-version: semantic_version
|
||||||
report-format: report_format
|
report-format: report_format
|
||||||
token: github_token
|
token: github_token
|
||||||
username: username
|
username: username
|
||||||
working-directory: file_path
|
working-directory: file_path
|
||||||
overrides: {}
|
overrides:
|
||||||
|
mode: mode_enum
|
||||||
statistics:
|
statistics:
|
||||||
total_inputs: 14
|
total_inputs: 14
|
||||||
validated_inputs: 12
|
validated_inputs: 14
|
||||||
skipped_inputs: 0
|
skipped_inputs: 0
|
||||||
coverage_percentage: 86
|
coverage_percentage: 100
|
||||||
validation_coverage: 86
|
validation_coverage: 100
|
||||||
auto_detected: true
|
auto_detected: true
|
||||||
manual_review_required: false
|
manual_review_required: false
|
||||||
quality_indicators:
|
quality_indicators:
|
||||||
|
|||||||
@@ -31,68 +31,42 @@ class CustomValidator(BaseValidator):
|
|||||||
valid = True
|
valid = True
|
||||||
|
|
||||||
# Validate python-version if provided
|
# Validate python-version if provided
|
||||||
if "python-version" in inputs or "python_version" in inputs:
|
version_key = self.get_key_variant(inputs, "python-version", "python_version")
|
||||||
key = "python-version" if "python-version" in inputs else "python_version"
|
if version_key:
|
||||||
value = inputs[key]
|
value = inputs[version_key]
|
||||||
|
if not value:
|
||||||
# Empty string should fail validation
|
|
||||||
if value == "":
|
|
||||||
self.add_error("Python version cannot be empty")
|
self.add_error("Python version cannot be empty")
|
||||||
valid = False
|
valid = False
|
||||||
elif value:
|
else:
|
||||||
result = self.version_validator.validate_python_version(value, key)
|
valid &= self.validate_with(
|
||||||
|
self.version_validator, "validate_python_version", value, version_key
|
||||||
# Propagate errors from the version validator
|
)
|
||||||
for error in self.version_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
|
|
||||||
self.version_validator.clear_errors()
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate username
|
# Validate username
|
||||||
if "username" in inputs:
|
if inputs.get("username"):
|
||||||
username = inputs["username"]
|
username = inputs["username"]
|
||||||
if username:
|
if len(username) > 39:
|
||||||
# Check username length (GitHub usernames are max 39 characters)
|
self.add_error("Username is too long (max 39 characters)")
|
||||||
if len(username) > 39:
|
valid = False
|
||||||
self.add_error("Username is too long (max 39 characters)")
|
if ";" in username or "`" in username or "$" in username:
|
||||||
valid = False
|
self.add_error("Username contains potentially dangerous characters")
|
||||||
# Check for command injection patterns
|
valid = False
|
||||||
if ";" in username or "`" in username or "$" in username:
|
|
||||||
self.add_error("Username contains potentially dangerous characters")
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate email
|
# Validate email
|
||||||
if "email" in inputs:
|
if inputs.get("email"):
|
||||||
email = inputs["email"]
|
valid &= self.validate_with(
|
||||||
if email:
|
self.network_validator, "validate_email", inputs["email"], "email"
|
||||||
result = self.network_validator.validate_email(email, "email")
|
)
|
||||||
for error in self.network_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.network_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate token
|
# Validate token
|
||||||
if "token" in inputs:
|
if inputs.get("token"):
|
||||||
token = inputs["token"]
|
token = inputs["token"]
|
||||||
if token:
|
# Check for variable expansion (but allow GitHub Actions expressions)
|
||||||
# Check for variable expansion (but allow GitHub Actions expressions)
|
if "${" in token and not token.startswith("${{ ") and not token.endswith(" }}"):
|
||||||
if "${" in token and not token.startswith("${{ ") and not token.endswith(" }}"):
|
self.add_error("Token contains potentially dangerous variable expansion")
|
||||||
self.add_error("Token contains potentially dangerous variable expansion")
|
valid = False
|
||||||
valid = False
|
else:
|
||||||
else:
|
valid &= self.validate_with(self.token_validator, "validate_github_token", token)
|
||||||
result = self.token_validator.validate_github_token(token)
|
|
||||||
for error in self.token_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Validate Inputs
|
- name: Validate Inputs
|
||||||
id: validate
|
id: validate
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: 'python-lint-fix'
|
action-type: 'python-lint-fix'
|
||||||
token: ${{ inputs.token }}
|
token: ${{ inputs.token }}
|
||||||
@@ -224,7 +224,7 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Python (pip)
|
- name: Setup Python (pip)
|
||||||
if: steps.package-manager.outputs.package-manager == 'pip'
|
if: steps.package-manager.outputs.package-manager == 'pip'
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ steps.python-version.outputs.detected-version }}
|
python-version: ${{ steps.python-version.outputs.detected-version }}
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
@@ -237,7 +237,7 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Python (pipenv)
|
- name: Setup Python (pipenv)
|
||||||
if: steps.package-manager.outputs.package-manager == 'pipenv'
|
if: steps.package-manager.outputs.package-manager == 'pipenv'
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ steps.python-version.outputs.detected-version }}
|
python-version: ${{ steps.python-version.outputs.detected-version }}
|
||||||
cache: 'pipenv'
|
cache: 'pipenv'
|
||||||
@@ -247,7 +247,7 @@ runs:
|
|||||||
|
|
||||||
- name: Setup Python (poetry)
|
- name: Setup Python (poetry)
|
||||||
if: steps.package-manager.outputs.package-manager == 'poetry'
|
if: steps.package-manager.outputs.package-manager == 'poetry'
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ steps.python-version.outputs.detected-version }}
|
python-version: ${{ steps.python-version.outputs.detected-version }}
|
||||||
cache: 'poetry'
|
cache: 'poetry'
|
||||||
@@ -361,7 +361,7 @@ runs:
|
|||||||
|
|
||||||
- name: Commit Fixes
|
- name: Commit Fixes
|
||||||
if: ${{ fromJSON(steps.fix.outputs.fixed_count) > 0 }}
|
if: ${{ fromJSON(steps.fix.outputs.fixed_count) > 0 }}
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||||
with:
|
with:
|
||||||
commit_message: 'style: apply python lint fixes'
|
commit_message: 'style: apply python lint fixes'
|
||||||
commit_user_name: ${{ inputs.username }}
|
commit_user_name: ${{ inputs.username }}
|
||||||
@@ -370,7 +370,7 @@ runs:
|
|||||||
|
|
||||||
- name: Upload SARIF Report
|
- name: Upload SARIF Report
|
||||||
if: steps.check-files.outputs.result == 'found'
|
if: steps.check-files.outputs.result == 'found'
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ inputs.working-directory }}/reports/flake8.sarif
|
sarif_file: ${{ inputs.working-directory }}/reports/flake8.sarif
|
||||||
category: 'python-lint'
|
category: 'python-lint'
|
||||||
|
|||||||
82
security-scan/README.md
Normal file
82
security-scan/README.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# ivuorinen/actions/security-scan
|
||||||
|
|
||||||
|
## Security Scan
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
Comprehensive security scanning for GitHub Actions including actionlint,
|
||||||
|
Gitleaks (optional), and Trivy vulnerability scanning. Requires
|
||||||
|
'security-events: write' and 'contents: read' permissions in the workflow.
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
| name | description | required | default |
|
||||||
|
|----------------------|--------------------------------------------------------------|----------|----------------------|
|
||||||
|
| `gitleaks-license` | <p>Gitleaks license key (required for Gitleaks scanning)</p> | `false` | `""` |
|
||||||
|
| `gitleaks-config` | <p>Path to Gitleaks config file</p> | `false` | `.gitleaks.toml` |
|
||||||
|
| `trivy-severity` | <p>Severity levels to scan for (comma-separated)</p> | `false` | `CRITICAL,HIGH` |
|
||||||
|
| `trivy-scanners` | <p>Types of scanners to run (comma-separated)</p> | `false` | `vuln,config,secret` |
|
||||||
|
| `trivy-timeout` | <p>Timeout for Trivy scan</p> | `false` | `10m` |
|
||||||
|
| `actionlint-enabled` | <p>Enable actionlint scanning</p> | `false` | `true` |
|
||||||
|
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
|
||||||
|
| name | description |
|
||||||
|
|------------------------|-----------------------------------------------------|
|
||||||
|
| `has_trivy_results` | <p>Whether Trivy scan produced valid results</p> |
|
||||||
|
| `has_gitleaks_results` | <p>Whether Gitleaks scan produced valid results</p> |
|
||||||
|
| `total_issues` | <p>Total number of security issues found</p> |
|
||||||
|
| `critical_issues` | <p>Number of critical security issues found</p> |
|
||||||
|
|
||||||
|
### Runs
|
||||||
|
|
||||||
|
This action is a `composite` action.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: ivuorinen/actions/security-scan@main
|
||||||
|
with:
|
||||||
|
gitleaks-license:
|
||||||
|
# Gitleaks license key (required for Gitleaks scanning)
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: ""
|
||||||
|
|
||||||
|
gitleaks-config:
|
||||||
|
# Path to Gitleaks config file
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: .gitleaks.toml
|
||||||
|
|
||||||
|
trivy-severity:
|
||||||
|
# Severity levels to scan for (comma-separated)
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: CRITICAL,HIGH
|
||||||
|
|
||||||
|
trivy-scanners:
|
||||||
|
# Types of scanners to run (comma-separated)
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: vuln,config,secret
|
||||||
|
|
||||||
|
trivy-timeout:
|
||||||
|
# Timeout for Trivy scan
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: 10m
|
||||||
|
|
||||||
|
actionlint-enabled:
|
||||||
|
# Enable actionlint scanning
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: true
|
||||||
|
|
||||||
|
token:
|
||||||
|
# GitHub token for authentication
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: ""
|
||||||
|
```
|
||||||
282
security-scan/action.yml
Normal file
282
security-scan/action.yml
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
|
||||||
|
#
|
||||||
|
# REQUIRED PERMISSIONS (set these in your workflow file):
|
||||||
|
# permissions:
|
||||||
|
# security-events: write # Required for SARIF uploads
|
||||||
|
# contents: read # Required for repository access
|
||||||
|
#
|
||||||
|
---
|
||||||
|
name: Security Scan
|
||||||
|
description: |
|
||||||
|
Comprehensive security scanning for GitHub Actions including actionlint,
|
||||||
|
Gitleaks (optional), and Trivy vulnerability scanning. Requires
|
||||||
|
'security-events: write' and 'contents: read' permissions in the workflow.
|
||||||
|
author: Ismo Vuorinen
|
||||||
|
branding:
|
||||||
|
icon: shield
|
||||||
|
color: red
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
gitleaks-license:
|
||||||
|
description: 'Gitleaks license key (required for Gitleaks scanning)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
gitleaks-config:
|
||||||
|
description: 'Path to Gitleaks config file'
|
||||||
|
required: false
|
||||||
|
default: '.gitleaks.toml'
|
||||||
|
trivy-severity:
|
||||||
|
description: 'Severity levels to scan for (comma-separated)'
|
||||||
|
required: false
|
||||||
|
default: 'CRITICAL,HIGH'
|
||||||
|
trivy-scanners:
|
||||||
|
description: 'Types of scanners to run (comma-separated)'
|
||||||
|
required: false
|
||||||
|
default: 'vuln,config,secret'
|
||||||
|
trivy-timeout:
|
||||||
|
description: 'Timeout for Trivy scan'
|
||||||
|
required: false
|
||||||
|
default: '10m'
|
||||||
|
actionlint-enabled:
|
||||||
|
description: 'Enable actionlint scanning'
|
||||||
|
required: false
|
||||||
|
default: 'true'
|
||||||
|
token:
|
||||||
|
description: 'GitHub token for authentication'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
has_trivy_results:
|
||||||
|
description: 'Whether Trivy scan produced valid results'
|
||||||
|
value: ${{ steps.verify-sarif.outputs.has_trivy }}
|
||||||
|
has_gitleaks_results:
|
||||||
|
description: 'Whether Gitleaks scan produced valid results'
|
||||||
|
value: ${{ steps.verify-sarif.outputs.has_gitleaks }}
|
||||||
|
total_issues:
|
||||||
|
description: 'Total number of security issues found'
|
||||||
|
value: ${{ steps.analyze.outputs.total_issues }}
|
||||||
|
critical_issues:
|
||||||
|
description: 'Number of critical security issues found'
|
||||||
|
value: ${{ steps.analyze.outputs.critical_issues }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Validate Inputs
|
||||||
|
id: validate
|
||||||
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
|
with:
|
||||||
|
action-type: security-scan
|
||||||
|
gitleaks-license: ${{ inputs.gitleaks-license }}
|
||||||
|
gitleaks-config: ${{ inputs.gitleaks-config }}
|
||||||
|
trivy-severity: ${{ inputs.trivy-severity }}
|
||||||
|
trivy-scanners: ${{ inputs.trivy-scanners }}
|
||||||
|
trivy-timeout: ${{ inputs.trivy-timeout }}
|
||||||
|
actionlint-enabled: ${{ inputs.actionlint-enabled }}
|
||||||
|
token: ${{ inputs.token }}
|
||||||
|
|
||||||
|
- name: Check Required Configurations
|
||||||
|
id: check-configs
|
||||||
|
shell: sh
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Initialize all flags as false
|
||||||
|
{
|
||||||
|
printf '%s\n' "run_gitleaks=false"
|
||||||
|
printf '%s\n' "run_trivy=true"
|
||||||
|
printf '%s\n' "run_actionlint=${{ inputs.actionlint-enabled }}"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Check Gitleaks configuration and license
|
||||||
|
if [ -f "${{ inputs.gitleaks-config }}" ] && [ -n "${{ inputs.gitleaks-license }}" ]; then
|
||||||
|
printf 'Gitleaks config and license found\n'
|
||||||
|
printf '%s\n' "run_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
printf '::warning::Gitleaks config or license missing - skipping Gitleaks scan\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run actionlint
|
||||||
|
if: steps.check-configs.outputs.run_actionlint == 'true'
|
||||||
|
uses: raven-actions/actionlint@e01d1ea33dd6a5ed517d95b4c0c357560ac6f518 # v2.1.1
|
||||||
|
with:
|
||||||
|
cache: true
|
||||||
|
fail-on-error: true
|
||||||
|
shellcheck: false
|
||||||
|
|
||||||
|
- name: Run Gitleaks
|
||||||
|
if: steps.check-configs.outputs.run_gitleaks == 'true'
|
||||||
|
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ inputs.token || github.token }}
|
||||||
|
GITLEAKS_LICENSE: ${{ inputs.gitleaks-license }}
|
||||||
|
with:
|
||||||
|
config-path: ${{ inputs.gitleaks-config }}
|
||||||
|
report-format: sarif
|
||||||
|
report-path: gitleaks-report.sarif
|
||||||
|
|
||||||
|
- name: Run Trivy vulnerability scanner
|
||||||
|
if: steps.check-configs.outputs.run_trivy == 'true'
|
||||||
|
uses: aquasecurity/trivy-action@a11da62073708815958ea6d84f5650c78a3ef85b # master
|
||||||
|
with:
|
||||||
|
scan-type: 'fs'
|
||||||
|
scanners: ${{ inputs.trivy-scanners }}
|
||||||
|
format: 'sarif'
|
||||||
|
output: 'trivy-results.sarif'
|
||||||
|
severity: ${{ inputs.trivy-severity }}
|
||||||
|
timeout: ${{ inputs.trivy-timeout }}
|
||||||
|
|
||||||
|
- name: Verify SARIF files
|
||||||
|
id: verify-sarif
|
||||||
|
shell: sh
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Initialize outputs
|
||||||
|
{
|
||||||
|
printf '%s\n' "has_trivy=false"
|
||||||
|
printf '%s\n' "has_gitleaks=false"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Check Trivy results
|
||||||
|
if [ -f "trivy-results.sarif" ]; then
|
||||||
|
if jq -e . <"trivy-results.sarif" >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "has_trivy=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
printf '::warning::Trivy SARIF file exists but is not valid JSON\n'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Gitleaks results if it ran
|
||||||
|
if [ "${{ steps.check-configs.outputs.run_gitleaks }}" = "true" ]; then
|
||||||
|
if [ -f "gitleaks-report.sarif" ]; then
|
||||||
|
if jq -e . <"gitleaks-report.sarif" >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "has_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
printf '::warning::Gitleaks SARIF file exists but is not valid JSON\n'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Trivy results
|
||||||
|
if: steps.verify-sarif.outputs.has_trivy == 'true'
|
||||||
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
|
with:
|
||||||
|
sarif_file: 'trivy-results.sarif'
|
||||||
|
category: 'trivy'
|
||||||
|
|
||||||
|
- name: Upload Gitleaks results
|
||||||
|
if: steps.verify-sarif.outputs.has_gitleaks == 'true'
|
||||||
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
|
with:
|
||||||
|
sarif_file: 'gitleaks-report.sarif'
|
||||||
|
category: 'gitleaks'
|
||||||
|
|
||||||
|
- name: Archive security reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
|
with:
|
||||||
|
name: security-reports-${{ github.run_id }}
|
||||||
|
path: |
|
||||||
|
${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'trivy-results.sarif' || '' }}
|
||||||
|
${{ steps.verify-sarif.outputs.has_gitleaks == 'true' && 'gitleaks-report.sarif' || '' }}
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Analyze Results
|
||||||
|
id: analyze
|
||||||
|
if: always()
|
||||||
|
shell: node {0}
|
||||||
|
run: |
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
try {
|
||||||
|
let totalIssues = 0;
|
||||||
|
let criticalIssues = 0;
|
||||||
|
|
||||||
|
const analyzeSarif = (file, tool) => {
|
||||||
|
if (!fs.existsSync(file)) {
|
||||||
|
console.log(`No results file found for ${tool}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sarif = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||||
|
return sarif.runs.reduce((acc, run) => {
|
||||||
|
if (!run.results) return acc;
|
||||||
|
|
||||||
|
const critical = run.results.filter(r =>
|
||||||
|
r.level === 'error' ||
|
||||||
|
r.level === 'critical' ||
|
||||||
|
(r.ruleId || '').toLowerCase().includes('critical')
|
||||||
|
).length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: acc.total + run.results.length,
|
||||||
|
critical: acc.critical + critical
|
||||||
|
};
|
||||||
|
}, { total: 0, critical: 0 });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Error analyzing ${tool} results: ${error.message}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only analyze results from tools that ran successfully
|
||||||
|
const results = {
|
||||||
|
trivy: '${{ steps.verify-sarif.outputs.has_trivy }}' === 'true' ?
|
||||||
|
analyzeSarif('trivy-results.sarif', 'trivy') : null,
|
||||||
|
gitleaks: '${{ steps.verify-sarif.outputs.has_gitleaks }}' === 'true' ?
|
||||||
|
analyzeSarif('gitleaks-report.sarif', 'gitleaks') : null
|
||||||
|
};
|
||||||
|
|
||||||
|
// Aggregate results
|
||||||
|
Object.entries(results).forEach(([tool, result]) => {
|
||||||
|
if (result) {
|
||||||
|
totalIssues += result.total;
|
||||||
|
criticalIssues += result.critical;
|
||||||
|
console.log(`${tool}: ${result.total} total, ${result.critical} critical issues`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create summary
|
||||||
|
const summary = `## Security Scan Summary
|
||||||
|
|
||||||
|
- Total Issues Found: ${totalIssues}
|
||||||
|
- Critical Issues: ${criticalIssues}
|
||||||
|
|
||||||
|
### Tool Breakdown
|
||||||
|
${Object.entries(results)
|
||||||
|
.filter(([_, r]) => r)
|
||||||
|
.map(([tool, r]) =>
|
||||||
|
`- ${tool}: ${r.total} total, ${r.critical} critical`
|
||||||
|
).join('\n')}
|
||||||
|
|
||||||
|
### Tools Run Status
|
||||||
|
- Actionlint: ${{ steps.check-configs.outputs.run_actionlint }}
|
||||||
|
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy }}
|
||||||
|
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks }}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Set outputs using GITHUB_OUTPUT
|
||||||
|
const outputFile = process.env.GITHUB_OUTPUT;
|
||||||
|
if (outputFile) {
|
||||||
|
fs.appendFileSync(outputFile, `total_issues=${totalIssues}\n`);
|
||||||
|
fs.appendFileSync(outputFile, `critical_issues=${criticalIssues}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job summary using GITHUB_STEP_SUMMARY
|
||||||
|
const summaryFile = process.env.GITHUB_STEP_SUMMARY;
|
||||||
|
if (summaryFile) {
|
||||||
|
fs.appendFileSync(summaryFile, summary + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if critical issues found
|
||||||
|
if (criticalIssues > 0) {
|
||||||
|
console.error(`Found ${criticalIssues} critical security issues`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Analysis failed: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
55
security-scan/rules.yml
Normal file
55
security-scan/rules.yml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
# Validation rules for security-scan action
|
||||||
|
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||||
|
# Schema version: 1.0
|
||||||
|
# Coverage: 86% (6/7 inputs)
|
||||||
|
#
|
||||||
|
# This file defines validation rules for the security-scan GitHub Action.
|
||||||
|
# Rules are automatically applied by validate-inputs action when this
|
||||||
|
# action is used.
|
||||||
|
#
|
||||||
|
|
||||||
|
schema_version: '1.0'
|
||||||
|
action: security-scan
|
||||||
|
description: |
|
||||||
|
Comprehensive security scanning for GitHub Actions including actionlint,
|
||||||
|
Gitleaks (optional), and Trivy vulnerability scanning. Requires
|
||||||
|
'security-events: write' and 'contents: read' permissions in the workflow.
|
||||||
|
generator_version: 1.0.0
|
||||||
|
required_inputs: []
|
||||||
|
optional_inputs:
|
||||||
|
- actionlint-enabled
|
||||||
|
- gitleaks-config
|
||||||
|
- gitleaks-license
|
||||||
|
- token
|
||||||
|
- trivy-scanners
|
||||||
|
- trivy-severity
|
||||||
|
- trivy-timeout
|
||||||
|
conventions:
|
||||||
|
actionlint-enabled: boolean
|
||||||
|
gitleaks-config: file_path
|
||||||
|
token: github_token
|
||||||
|
trivy-scanners: scanner_list
|
||||||
|
trivy-severity: severity_enum
|
||||||
|
trivy-timeout: timeout_with_unit
|
||||||
|
overrides:
|
||||||
|
actionlint-enabled: boolean
|
||||||
|
gitleaks-config: file_path
|
||||||
|
token: github_token
|
||||||
|
trivy-scanners: scanner_list
|
||||||
|
trivy-severity: severity_enum
|
||||||
|
trivy-timeout: timeout_with_unit
|
||||||
|
statistics:
|
||||||
|
total_inputs: 7
|
||||||
|
validated_inputs: 6
|
||||||
|
skipped_inputs: 0
|
||||||
|
coverage_percentage: 86
|
||||||
|
validation_coverage: 86
|
||||||
|
auto_detected: true
|
||||||
|
manual_review_required: false
|
||||||
|
quality_indicators:
|
||||||
|
has_required_inputs: false
|
||||||
|
has_token_validation: true
|
||||||
|
has_version_validation: false
|
||||||
|
has_file_validation: true
|
||||||
|
has_security_validation: true
|
||||||
@@ -43,7 +43,7 @@ runs:
|
|||||||
|
|
||||||
- name: Validate Inputs
|
- name: Validate Inputs
|
||||||
id: validate
|
id: validate
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: 'stale'
|
action-type: 'stale'
|
||||||
token: ${{ inputs.token || github.token }}
|
token: ${{ inputs.token || github.token }}
|
||||||
@@ -52,7 +52,7 @@ runs:
|
|||||||
|
|
||||||
- name: 🚀 Run stale
|
- name: 🚀 Run stale
|
||||||
id: stale
|
id: stale
|
||||||
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ inputs.token || github.token }}
|
repo-token: ${{ inputs.token || github.token }}
|
||||||
days-before-stale: ${{ inputs.days-before-stale }}
|
days-before-stale: ${{ inputs.days-before-stale }}
|
||||||
|
|||||||
@@ -78,16 +78,9 @@ class CustomValidator(BaseValidator):
|
|||||||
|
|
||||||
# Validate token if provided
|
# Validate token if provided
|
||||||
if "token" in inputs:
|
if "token" in inputs:
|
||||||
token_valid = self.token_validator.validate_github_token(
|
valid &= self.validate_with(
|
||||||
inputs["token"],
|
self.token_validator, "validate_github_token", inputs["token"], required=False
|
||||||
required=False, # Token is optional, defaults to ${{ github.token }}
|
|
||||||
)
|
)
|
||||||
# Copy any errors from token validator
|
|
||||||
for error in self.token_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
valid &= token_valid
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
@@ -100,27 +93,15 @@ class CustomValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
# Allow GitHub Actions expressions
|
|
||||||
if self.is_github_expression(path):
|
if self.is_github_expression(path):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# First check basic file path security
|
result = self.validate_with(self.file_validator, "validate_file_path", path, "labels")
|
||||||
result = self.file_validator.validate_file_path(path, "labels")
|
|
||||||
# Copy any errors from file validator
|
|
||||||
for error in self.file_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check file extension
|
|
||||||
if not (path.endswith(".yml") or path.endswith(".yaml")):
|
if not (path.endswith(".yml") or path.endswith(".yaml")):
|
||||||
self.add_error(f'Invalid labels file: "{path}". Must be a .yml or .yaml file')
|
self.add_error(f'Invalid labels file: "{path}". Must be a .yml or .yaml file')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Additional custom validation could go here
|
|
||||||
# For example, checking if the file exists, validating YAML structure, etc.
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -30,54 +30,32 @@ class CustomValidator(BaseValidator):
|
|||||||
"""Validate terraform-lint-fix action inputs."""
|
"""Validate terraform-lint-fix action inputs."""
|
||||||
valid = True
|
valid = True
|
||||||
|
|
||||||
# Validate terraform-version if provided
|
# Validate terraform-version if provided (empty is OK - uses default)
|
||||||
if "terraform-version" in inputs:
|
if inputs.get("terraform-version"):
|
||||||
value = inputs["terraform-version"]
|
valid &= self.validate_with(
|
||||||
|
self.version_validator,
|
||||||
|
"validate_terraform_version",
|
||||||
|
inputs["terraform-version"],
|
||||||
|
"terraform-version",
|
||||||
|
)
|
||||||
|
|
||||||
# Empty string is OK - uses default
|
# Validate token if provided (empty is OK - uses default)
|
||||||
if value == "":
|
if inputs.get("token"):
|
||||||
pass # Allow empty, will use default
|
valid &= self.validate_with(
|
||||||
elif value:
|
self.token_validator,
|
||||||
result = self.version_validator.validate_terraform_version(
|
"validate_github_token",
|
||||||
value, "terraform-version"
|
inputs["token"],
|
||||||
)
|
required=False,
|
||||||
|
)
|
||||||
# Propagate errors from the version validator
|
|
||||||
for error in self.version_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
|
|
||||||
self.version_validator.clear_errors()
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate token if provided
|
|
||||||
if "token" in inputs:
|
|
||||||
value = inputs["token"]
|
|
||||||
if value == "":
|
|
||||||
# Empty token is OK - uses default
|
|
||||||
pass
|
|
||||||
elif value:
|
|
||||||
result = self.token_validator.validate_github_token(value, required=False)
|
|
||||||
for error in self.token_validator.errors:
|
|
||||||
if error not in self.errors:
|
|
||||||
self.add_error(error)
|
|
||||||
self.token_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate working-directory if provided
|
# Validate working-directory if provided
|
||||||
if "working-directory" in inputs:
|
if inputs.get("working-directory"):
|
||||||
value = inputs["working-directory"]
|
valid &= self.validate_with(
|
||||||
if value:
|
self.file_validator,
|
||||||
result = self.file_validator.validate_file_path(value, "working-directory")
|
"validate_file_path",
|
||||||
for error in self.file_validator.errors:
|
inputs["working-directory"],
|
||||||
if error not in self.errors:
|
"working-directory",
|
||||||
self.add_error(error)
|
)
|
||||||
self.file_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ runs:
|
|||||||
|
|
||||||
- name: Validate Inputs
|
- name: Validate Inputs
|
||||||
id: validate
|
id: validate
|
||||||
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8
|
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
|
||||||
with:
|
with:
|
||||||
action-type: 'terraform-lint-fix'
|
action-type: 'terraform-lint-fix'
|
||||||
token: ${{ inputs.token || github.token }}
|
token: ${{ inputs.token || github.token }}
|
||||||
@@ -247,7 +247,7 @@ runs:
|
|||||||
|
|
||||||
- name: Commit Fixes
|
- name: Commit Fixes
|
||||||
if: steps.check-files.outputs.found == 'true' && inputs.auto-fix == 'true' && fromJSON(steps.fix.outputs.fixed_count) > 0
|
if: steps.check-files.outputs.found == 'true' && inputs.auto-fix == 'true' && fromJSON(steps.fix.outputs.fixed_count) > 0
|
||||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||||
with:
|
with:
|
||||||
commit_message: 'style: apply terraform formatting fixes'
|
commit_message: 'style: apply terraform formatting fixes'
|
||||||
commit_user_name: ${{ inputs.username }}
|
commit_user_name: ${{ inputs.username }}
|
||||||
@@ -256,7 +256,7 @@ runs:
|
|||||||
|
|
||||||
- name: Upload SARIF Report
|
- name: Upload SARIF Report
|
||||||
if: steps.check-files.outputs.found == 'true' && inputs.format == 'sarif'
|
if: steps.check-files.outputs.found == 'true' && inputs.format == 'sarif'
|
||||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ env.VALIDATED_WORKING_DIR }}/reports/tflint.sarif
|
sarif_file: ${{ env.VALIDATED_WORKING_DIR }}/reports/tflint.sarif
|
||||||
category: terraform-lint
|
category: terraform-lint
|
||||||
|
|||||||
440
uv.lock
generated
440
uv.lock
generated
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 3
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -13,87 +13,115 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "7.10.4"
|
version = "7.13.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/4e/08b493f1f1d8a5182df0044acc970799b58a8d289608e0d891a03e9d269a/coverage-7.10.4.tar.gz", hash = "sha256:25f5130af6c8e7297fd14634955ba9e1697f47143f289e2a23284177c0061d27", size = 823798, upload-time = "2025-08-17T00:26:43.314Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/9c/f4/350759710db50362685f922259c140592dba15eb4e2325656a98413864d9/coverage-7.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d92d6edb0ccafd20c6fbf9891ca720b39c2a6a4b4a6f9cf323ca2c986f33e475", size = 216403, upload-time = "2025-08-17T00:24:19.083Z" },
|
{ url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/7e/e467c2bb4d5ecfd166bfd22c405cce4c50de2763ba1d78e2729c59539a42/coverage-7.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7202da14dc0236884fcc45665ffb2d79d4991a53fbdf152ab22f69f70923cc22", size = 216802, upload-time = "2025-08-17T00:24:21.824Z" },
|
{ url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/ab/2accdd1ccfe63b890e5eb39118f63c155202df287798364868a2884a50af/coverage-7.10.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ada418633ae24ec8d0fcad5efe6fc7aa3c62497c6ed86589e57844ad04365674", size = 243558, upload-time = "2025-08-17T00:24:23.569Z" },
|
{ url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/04/c14c33d0cfc0f4db6b3504d01a47f4c798563d932a836fd5f2dbc0521d3d/coverage-7.10.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b828e33eca6c3322adda3b5884456f98c435182a44917ded05005adfa1415500", size = 245370, upload-time = "2025-08-17T00:24:24.858Z" },
|
{ url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/71/147053061f1f51c1d3b3d040c3cb26876964a3a0dca0765d2441411ca568/coverage-7.10.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:802793ba397afcfdbe9f91f89d65ae88b958d95edc8caf948e1f47d8b6b2b606", size = 247228, upload-time = "2025-08-17T00:24:26.167Z" },
|
{ url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/92/7ef882205d4d4eb502e6154ee7122c1a1b1ce3f29d0166921e0fb550a5d3/coverage-7.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d0b23512338c54101d3bf7a1ab107d9d75abda1d5f69bc0887fd079253e4c27e", size = 245270, upload-time = "2025-08-17T00:24:27.424Z" },
|
{ url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/3d/297a20603abcc6c7d89d801286eb477b0b861f3c5a4222730f1c9837be3e/coverage-7.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f36b7dcf72d06a8c5e2dd3aca02be2b1b5db5f86404627dff834396efce958f2", size = 243287, upload-time = "2025-08-17T00:24:28.697Z" },
|
{ url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/65/f9/b04111438f41f1ddd5dc88706d5f8064ae5bb962203c49fe417fa23a362d/coverage-7.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fce316c367a1dc2c411821365592eeb335ff1781956d87a0410eae248188ba51", size = 244164, upload-time = "2025-08-17T00:24:30.393Z" },
|
{ url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/e5/c7d9eb7a9ea66cf92d069077719fb2b07782dcd7050b01a9b88766b52154/coverage-7.10.4-cp310-cp310-win32.whl", hash = "sha256:8c5dab29fc8070b3766b5fc85f8d89b19634584429a2da6d42da5edfadaf32ae", size = 218917, upload-time = "2025-08-17T00:24:31.67Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/66/30/4d9d3b81f5a836b31a7428b8a25e6d490d4dca5ff2952492af130153c35c/coverage-7.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:4b0d114616f0fccb529a1817457d5fb52a10e106f86c5fb3b0bd0d45d0d69b93", size = 219822, upload-time = "2025-08-17T00:24:32.89Z" },
|
{ url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/ba/2c9817e62018e7d480d14f684c160b3038df9ff69c5af7d80e97d143e4d1/coverage-7.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:05d5f98ec893d4a2abc8bc5f046f2f4367404e7e5d5d18b83de8fde1093ebc4f", size = 216514, upload-time = "2025-08-17T00:24:34.188Z" },
|
{ url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/5a/093412a959a6b6261446221ba9fb23bb63f661a5de70b5d130763c87f916/coverage-7.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9267efd28f8994b750d171e58e481e3bbd69e44baed540e4c789f8e368b24b88", size = 216914, upload-time = "2025-08-17T00:24:35.881Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/1f/2fdf4a71cfe93b07eae845ebf763267539a7d8b7e16b062f959d56d7e433/coverage-7.10.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4456a039fdc1a89ea60823d0330f1ac6f97b0dbe9e2b6fb4873e889584b085fb", size = 247308, upload-time = "2025-08-17T00:24:37.61Z" },
|
{ url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/16/33f6cded458e84f008b9f6bc379609a6a1eda7bffe349153b9960803fc11/coverage-7.10.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c2bfbd2a9f7e68a21c5bd191be94bfdb2691ac40d325bac9ef3ae45ff5c753d9", size = 249241, upload-time = "2025-08-17T00:24:38.919Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/84/98/9c18e47c889be58339ff2157c63b91a219272503ee32b49d926eea2337f2/coverage-7.10.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab7765f10ae1df7e7fe37de9e64b5a269b812ee22e2da3f84f97b1c7732a0d8", size = 251346, upload-time = "2025-08-17T00:24:40.507Z" },
|
{ url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/07/00a6c0d53e9a22d36d8e95ddd049b860eef8f4b9fd299f7ce34d8e323356/coverage-7.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a09b13695166236e171ec1627ff8434b9a9bae47528d0ba9d944c912d33b3d2", size = 249037, upload-time = "2025-08-17T00:24:41.904Z" },
|
{ url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/0e/1e1b944d6a6483d07bab5ef6ce063fcf3d0cc555a16a8c05ebaab11f5607/coverage-7.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5c9e75dfdc0167d5675e9804f04a56b2cf47fb83a524654297000b578b8adcb7", size = 247090, upload-time = "2025-08-17T00:24:43.193Z" },
|
{ url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/43/2ce5ab8a728b8e25ced077111581290ffaef9efaf860a28e25435ab925cf/coverage-7.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c751261bfe6481caba15ec005a194cb60aad06f29235a74c24f18546d8377df0", size = 247732, upload-time = "2025-08-17T00:24:44.906Z" },
|
{ url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/f3/706c4a24f42c1c5f3a2ca56637ab1270f84d9e75355160dc34d5e39bb5b7/coverage-7.10.4-cp311-cp311-win32.whl", hash = "sha256:051c7c9e765f003c2ff6e8c81ccea28a70fb5b0142671e4e3ede7cebd45c80af", size = 218961, upload-time = "2025-08-17T00:24:46.241Z" },
|
{ url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/aa/6b9ea06e0290bf1cf2a2765bba89d561c5c563b4e9db8298bf83699c8b67/coverage-7.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a647b152f10be08fb771ae4a1421dbff66141e3d8ab27d543b5eb9ea5af8e52", size = 219851, upload-time = "2025-08-17T00:24:48.795Z" },
|
{ url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/be/f0dc9ad50ee183369e643cd7ed8f2ef5c491bc20b4c3387cbed97dd6e0d1/coverage-7.10.4-cp311-cp311-win_arm64.whl", hash = "sha256:b09b9e4e1de0d406ca9f19a371c2beefe3193b542f64a6dd40cfcf435b7d6aa0", size = 218530, upload-time = "2025-08-17T00:24:50.164Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/4a/781c9e4dd57cabda2a28e2ce5b00b6be416015265851060945a5ed4bd85e/coverage-7.10.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a1f0264abcabd4853d4cb9b3d164adbf1565da7dab1da1669e93f3ea60162d79", size = 216706, upload-time = "2025-08-17T00:24:51.528Z" },
|
{ url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/8c/51255202ca03d2e7b664770289f80db6f47b05138e06cce112b3957d5dfd/coverage-7.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:536cbe6b118a4df231b11af3e0f974a72a095182ff8ec5f4868c931e8043ef3e", size = 216939, upload-time = "2025-08-17T00:24:53.171Z" },
|
{ url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/7f/df11131483698660f94d3c847dc76461369782d7a7644fcd72ac90da8fd0/coverage-7.10.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9a4c0d84134797b7bf3f080599d0cd501471f6c98b715405166860d79cfaa97e", size = 248429, upload-time = "2025-08-17T00:24:54.934Z" },
|
{ url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/fa/13ac5eda7300e160bf98f082e75f5c5b4189bf3a883dd1ee42dbedfdc617/coverage-7.10.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7c155fc0f9cee8c9803ea0ad153ab6a3b956baa5d4cd993405dc0b45b2a0b9e0", size = 251178, upload-time = "2025-08-17T00:24:56.353Z" },
|
{ url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/bc/f63b56a58ad0bec68a840e7be6b7ed9d6f6288d790760647bb88f5fea41e/coverage-7.10.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5f2ab6e451d4b07855d8bcf063adf11e199bff421a4ba57f5bb95b7444ca62", size = 252313, upload-time = "2025-08-17T00:24:57.692Z" },
|
{ url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/b6/79338f1ea27b01266f845afb4485976211264ab92407d1c307babe3592a7/coverage-7.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:685b67d99b945b0c221be0780c336b303a7753b3e0ec0d618c795aada25d5e7a", size = 250230, upload-time = "2025-08-17T00:24:59.293Z" },
|
{ url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/93/3b24f1da3e0286a4dc5832427e1d448d5296f8287464b1ff4a222abeeeb5/coverage-7.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c079027e50c2ae44da51c2e294596cbc9dbb58f7ca45b30651c7e411060fc23", size = 248351, upload-time = "2025-08-17T00:25:00.676Z" },
|
{ url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/de/5f/d59412f869e49dcc5b89398ef3146c8bfaec870b179cc344d27932e0554b/coverage-7.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3749aa72b93ce516f77cf5034d8e3c0dfd45c6e8a163a602ede2dc5f9a0bb927", size = 249788, upload-time = "2025-08-17T00:25:02.354Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/52/04a3b733f40a0cc7c4a5b9b010844111dbf906df3e868b13e1ce7b39ac31/coverage-7.10.4-cp312-cp312-win32.whl", hash = "sha256:fecb97b3a52fa9bcd5a7375e72fae209088faf671d39fae67261f37772d5559a", size = 219131, upload-time = "2025-08-17T00:25:03.79Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/dd/12909fc0b83888197b3ec43a4ac7753589591c08d00d9deda4158df2734e/coverage-7.10.4-cp312-cp312-win_amd64.whl", hash = "sha256:26de58f355626628a21fe6a70e1e1fad95702dafebfb0685280962ae1449f17b", size = 219939, upload-time = "2025-08-17T00:25:05.494Z" },
|
{ url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/c7/058bb3220fdd6821bada9685eadac2940429ab3c97025ce53549ff423cc1/coverage-7.10.4-cp312-cp312-win_arm64.whl", hash = "sha256:67e8885408f8325198862bc487038a4980c9277d753cb8812510927f2176437a", size = 218572, upload-time = "2025-08-17T00:25:06.897Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/b0/4a3662de81f2ed792a4e425d59c4ae50d8dd1d844de252838c200beed65a/coverage-7.10.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b8e1d2015d5dfdbf964ecef12944c0c8c55b885bb5c0467ae8ef55e0e151233", size = 216735, upload-time = "2025-08-17T00:25:08.617Z" },
|
{ url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c5/e8/e2dcffea01921bfffc6170fb4406cffb763a3b43a047bbd7923566708193/coverage-7.10.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:25735c299439018d66eb2dccf54f625aceb78645687a05f9f848f6e6c751e169", size = 216982, upload-time = "2025-08-17T00:25:10.384Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/59/cc89bb6ac869704d2781c2f5f7957d07097c77da0e8fdd4fd50dbf2ac9c0/coverage-7.10.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:715c06cb5eceac4d9b7cdf783ce04aa495f6aff657543fea75c30215b28ddb74", size = 247981, upload-time = "2025-08-17T00:25:11.854Z" },
|
{ url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/23/3da089aa177ceaf0d3f96754ebc1318597822e6387560914cc480086e730/coverage-7.10.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e017ac69fac9aacd7df6dc464c05833e834dc5b00c914d7af9a5249fcccf07ef", size = 250584, upload-time = "2025-08-17T00:25:13.483Z" },
|
{ url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ad/82/e8693c368535b4e5fad05252a366a1794d481c79ae0333ed943472fd778d/coverage-7.10.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bad180cc40b3fccb0f0e8c702d781492654ac2580d468e3ffc8065e38c6c2408", size = 251856, upload-time = "2025-08-17T00:25:15.27Z" },
|
{ url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/19/8b9cb13292e602fa4135b10a26ac4ce169a7fc7c285ff08bedd42ff6acca/coverage-7.10.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:becbdcd14f685fada010a5f792bf0895675ecf7481304fe159f0cd3f289550bd", size = 250015, upload-time = "2025-08-17T00:25:16.759Z" },
|
{ url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/e7/e5903990ce089527cf1c4f88b702985bd65c61ac245923f1ff1257dbcc02/coverage-7.10.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0b485ca21e16a76f68060911f97ebbe3e0d891da1dbbce6af7ca1ab3f98b9097", size = 247908, upload-time = "2025-08-17T00:25:18.232Z" },
|
{ url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dd/c9/7d464f116df1df7fe340669af1ddbe1a371fc60f3082ff3dc837c4f1f2ab/coverage-7.10.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6c1d098ccfe8e1e0a1ed9a0249138899948afd2978cbf48eb1cc3fcd38469690", size = 249525, upload-time = "2025-08-17T00:25:20.141Z" },
|
{ url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/42/722e0cdbf6c19e7235c2020837d4e00f3b07820fd012201a983238cc3a30/coverage-7.10.4-cp313-cp313-win32.whl", hash = "sha256:8630f8af2ca84b5c367c3df907b1706621abe06d6929f5045fd628968d421e6e", size = 219173, upload-time = "2025-08-17T00:25:21.56Z" },
|
{ url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/7e/aa70366f8275955cd51fa1ed52a521c7fcebcc0fc279f53c8c1ee6006dfe/coverage-7.10.4-cp313-cp313-win_amd64.whl", hash = "sha256:f68835d31c421736be367d32f179e14ca932978293fe1b4c7a6a49b555dff5b2", size = 219969, upload-time = "2025-08-17T00:25:23.501Z" },
|
{ url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ac/96/c39d92d5aad8fec28d4606556bfc92b6fee0ab51e4a548d9b49fb15a777c/coverage-7.10.4-cp313-cp313-win_arm64.whl", hash = "sha256:6eaa61ff6724ca7ebc5326d1fae062d85e19b38dd922d50903702e6078370ae7", size = 218601, upload-time = "2025-08-17T00:25:25.295Z" },
|
{ url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/13/34d549a6177bd80fa5db758cb6fd3057b7ad9296d8707d4ab7f480b0135f/coverage-7.10.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:702978108876bfb3d997604930b05fe769462cc3000150b0e607b7b444f2fd84", size = 217445, upload-time = "2025-08-17T00:25:27.129Z" },
|
{ url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/c0/433da866359bf39bf595f46d134ff2d6b4293aeea7f3328b6898733b0633/coverage-7.10.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e8f978e8c5521d9c8f2086ac60d931d583fab0a16f382f6eb89453fe998e2484", size = 217676, upload-time = "2025-08-17T00:25:28.641Z" },
|
{ url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/d7/2b99aa8737f7801fd95222c79a4ebc8c5dd4460d4bed7ef26b17a60c8d74/coverage-7.10.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:df0ac2ccfd19351411c45e43ab60932b74472e4648b0a9edf6a3b58846e246a9", size = 259002, upload-time = "2025-08-17T00:25:30.065Z" },
|
{ url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/cf/86432b69d57debaef5abf19aae661ba8f4fcd2882fa762e14added4bd334/coverage-7.10.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:73a0d1aaaa3796179f336448e1576a3de6fc95ff4f07c2d7251d4caf5d18cf8d", size = 261178, upload-time = "2025-08-17T00:25:31.517Z" },
|
{ url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/23/78/85176593f4aa6e869cbed7a8098da3448a50e3fac5cb2ecba57729a5220d/coverage-7.10.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:873da6d0ed6b3ffc0bc01f2c7e3ad7e2023751c0d8d86c26fe7322c314b031dc", size = 263402, upload-time = "2025-08-17T00:25:33.339Z" },
|
{ url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/1d/57a27b6789b79abcac0cc5805b31320d7a97fa20f728a6a7c562db9a3733/coverage-7.10.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c6446c75b0e7dda5daa876a1c87b480b2b52affb972fedd6c22edf1aaf2e00ec", size = 260957, upload-time = "2025-08-17T00:25:34.795Z" },
|
{ url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/e5/3e5ddfd42835c6def6cd5b2bdb3348da2e34c08d9c1211e91a49e9fd709d/coverage-7.10.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6e73933e296634e520390c44758d553d3b573b321608118363e52113790633b9", size = 258718, upload-time = "2025-08-17T00:25:36.259Z" },
|
{ url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1a/0b/d364f0f7ef111615dc4e05a6ed02cac7b6f2ac169884aa57faeae9eb5fa0/coverage-7.10.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52073d4b08d2cb571234c8a71eb32af3c6923149cf644a51d5957ac128cf6aa4", size = 259848, upload-time = "2025-08-17T00:25:37.754Z" },
|
{ url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/c6/bbea60a3b309621162e53faf7fac740daaf083048ea22077418e1ecaba3f/coverage-7.10.4-cp313-cp313t-win32.whl", hash = "sha256:e24afb178f21f9ceb1aefbc73eb524769aa9b504a42b26857243f881af56880c", size = 219833, upload-time = "2025-08-17T00:25:39.252Z" },
|
{ url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/a5/f9f080d49cfb117ddffe672f21eab41bd23a46179a907820743afac7c021/coverage-7.10.4-cp313-cp313t-win_amd64.whl", hash = "sha256:be04507ff1ad206f4be3d156a674e3fb84bbb751ea1b23b142979ac9eebaa15f", size = 220897, upload-time = "2025-08-17T00:25:40.772Z" },
|
{ url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/89/49a3fc784fa73d707f603e586d84a18c2e7796707044e9d73d13260930b7/coverage-7.10.4-cp313-cp313t-win_arm64.whl", hash = "sha256:f3e3ff3f69d02b5dad67a6eac68cc9c71ae343b6328aae96e914f9f2f23a22e2", size = 219160, upload-time = "2025-08-17T00:25:42.229Z" },
|
{ url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/22/525f84b4cbcff66024d29f6909d7ecde97223f998116d3677cfba0d115b5/coverage-7.10.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a59fe0af7dd7211ba595cf7e2867458381f7e5d7b4cffe46274e0b2f5b9f4eb4", size = 216717, upload-time = "2025-08-17T00:25:43.875Z" },
|
{ url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a6/58/213577f77efe44333a416d4bcb251471e7f64b19b5886bb515561b5ce389/coverage-7.10.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a6c35c5b70f569ee38dc3350cd14fdd0347a8b389a18bb37538cc43e6f730e6", size = 216994, upload-time = "2025-08-17T00:25:45.405Z" },
|
{ url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/17/85/34ac02d0985a09472f41b609a1d7babc32df87c726c7612dc93d30679b5a/coverage-7.10.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:acb7baf49f513554c4af6ef8e2bd6e8ac74e6ea0c7386df8b3eb586d82ccccc4", size = 248038, upload-time = "2025-08-17T00:25:46.981Z" },
|
{ url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/47/4f/2140305ec93642fdaf988f139813629cbb6d8efa661b30a04b6f7c67c31e/coverage-7.10.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a89afecec1ed12ac13ed203238b560cbfad3522bae37d91c102e690b8b1dc46c", size = 250575, upload-time = "2025-08-17T00:25:48.613Z" },
|
{ url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/b5/41b5784180b82a083c76aeba8f2c72ea1cb789e5382157b7dc852832aea2/coverage-7.10.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:480442727f464407d8ade6e677b7f21f3b96a9838ab541b9a28ce9e44123c14e", size = 251927, upload-time = "2025-08-17T00:25:50.881Z" },
|
{ url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/ca/c1dd063e50b71f5aea2ebb27a1c404e7b5ecf5714c8b5301f20e4e8831ac/coverage-7.10.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a89bf193707f4a17f1ed461504031074d87f035153239f16ce86dfb8f8c7ac76", size = 249930, upload-time = "2025-08-17T00:25:52.422Z" },
|
{ url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/66/d8907408612ffee100d731798e6090aedb3ba766ecf929df296c1a7ee4fb/coverage-7.10.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3ddd912c2fc440f0fb3229e764feec85669d5d80a988ff1b336a27d73f63c818", size = 247862, upload-time = "2025-08-17T00:25:54.316Z" },
|
{ url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/db/53cd8ec8b1c9c52d8e22a25434785bfc2d1e70c0cfb4d278a1326c87f741/coverage-7.10.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a538944ee3a42265e61c7298aeba9ea43f31c01271cf028f437a7b4075592cf", size = 249360, upload-time = "2025-08-17T00:25:55.833Z" },
|
{ url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/75/5ec0a28ae4a0804124ea5a5becd2b0fa3adf30967ac656711fb5cdf67c60/coverage-7.10.4-cp314-cp314-win32.whl", hash = "sha256:fd2e6002be1c62476eb862b8514b1ba7e7684c50165f2a8d389e77da6c9a2ebd", size = 219449, upload-time = "2025-08-17T00:25:57.984Z" },
|
{ url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/ab/66e2ee085ec60672bf5250f11101ad8143b81f24989e8c0e575d16bb1e53/coverage-7.10.4-cp314-cp314-win_amd64.whl", hash = "sha256:ec113277f2b5cf188d95fb66a65c7431f2b9192ee7e6ec9b72b30bbfb53c244a", size = 220246, upload-time = "2025-08-17T00:25:59.868Z" },
|
{ url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/37/3b/00b448d385f149143190846217797d730b973c3c0ec2045a7e0f5db3a7d0/coverage-7.10.4-cp314-cp314-win_arm64.whl", hash = "sha256:9744954bfd387796c6a091b50d55ca7cac3d08767795b5eec69ad0f7dbf12d38", size = 218825, upload-time = "2025-08-17T00:26:01.44Z" },
|
{ url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/2e/55e20d3d1ce00b513efb6fd35f13899e1c6d4f76c6cbcc9851c7227cd469/coverage-7.10.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5af4829904dda6aabb54a23879f0f4412094ba9ef153aaa464e3c1b1c9bc98e6", size = 217462, upload-time = "2025-08-17T00:26:03.014Z" },
|
{ url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/47/b3/aab1260df5876f5921e2c57519e73a6f6eeacc0ae451e109d44ee747563e/coverage-7.10.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7bba5ed85e034831fac761ae506c0644d24fd5594727e174b5a73aff343a7508", size = 217675, upload-time = "2025-08-17T00:26:04.606Z" },
|
{ url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/23/1cfe2aa50c7026180989f0bfc242168ac7c8399ccc66eb816b171e0ab05e/coverage-7.10.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d57d555b0719834b55ad35045de6cc80fc2b28e05adb6b03c98479f9553b387f", size = 259176, upload-time = "2025-08-17T00:26:06.159Z" },
|
{ url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/72/5882b6aeed3f9de7fc4049874fd7d24213bf1d06882f5c754c8a682606ec/coverage-7.10.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ba62c51a72048bb1ea72db265e6bd8beaabf9809cd2125bbb5306c6ce105f214", size = 261341, upload-time = "2025-08-17T00:26:08.137Z" },
|
{ url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/70/a0c76e3087596ae155f8e71a49c2c534c58b92aeacaf4d9d0cbbf2dde53b/coverage-7.10.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0acf0c62a6095f07e9db4ec365cc58c0ef5babb757e54745a1aa2ea2a2564af1", size = 263600, upload-time = "2025-08-17T00:26:11.045Z" },
|
{ url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/5f/27e4cd4505b9a3c05257fb7fc509acbc778c830c450cb4ace00bf2b7bda7/coverage-7.10.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1033bf0f763f5cf49ffe6594314b11027dcc1073ac590b415ea93463466deec", size = 261036, upload-time = "2025-08-17T00:26:12.693Z" },
|
{ url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/d6/cf2ae3a7f90ab226ea765a104c4e76c5126f73c93a92eaea41e1dc6a1892/coverage-7.10.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:92c29eff894832b6a40da1789b1f252305af921750b03ee4535919db9179453d", size = 258794, upload-time = "2025-08-17T00:26:14.261Z" },
|
{ url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/b1/39f222eab0d78aa2001cdb7852aa1140bba632db23a5cfd832218b496d6c/coverage-7.10.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:822c4c830989c2093527e92acd97be4638a44eb042b1bdc0e7a278d84a070bd3", size = 259946, upload-time = "2025-08-17T00:26:15.899Z" },
|
{ url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/b2/49d82acefe2fe7c777436a3097f928c7242a842538b190f66aac01f29321/coverage-7.10.4-cp314-cp314t-win32.whl", hash = "sha256:e694d855dac2e7cf194ba33653e4ba7aad7267a802a7b3fc4347d0517d5d65cd", size = 220226, upload-time = "2025-08-17T00:26:17.566Z" },
|
{ url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/b0/afb942b6b2fc30bdbc7b05b087beae11c2b0daaa08e160586cf012b6ad70/coverage-7.10.4-cp314-cp314t-win_amd64.whl", hash = "sha256:efcc54b38ef7d5bfa98050f220b415bc5bb3d432bd6350a861cf6da0ede2cdcd", size = 221346, upload-time = "2025-08-17T00:26:19.311Z" },
|
{ url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d8/66/e0531c9d1525cb6eac5b5733c76f27f3053ee92665f83f8899516fea6e76/coverage-7.10.4-cp314-cp314t-win_arm64.whl", hash = "sha256:6f3a3496c0fa26bfac4ebc458747b778cff201c8ae94fa05e1391bab0dbc473c", size = 219368, upload-time = "2025-08-17T00:26:21.011Z" },
|
{ url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/78/983efd23200921d9edb6bd40512e1aa04af553d7d5a171e50f9b2b45d109/coverage-7.10.4-py3-none-any.whl", hash = "sha256:065d75447228d05121e5c938ca8f0e91eed60a1eb2d1258d42d5084fecfc3302", size = 208365, upload-time = "2025-08-17T00:26:41.479Z" },
|
{ url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
@@ -103,23 +131,23 @@ toml = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "exceptiongroup"
|
name = "exceptiongroup"
|
||||||
version = "1.3.0"
|
version = "1.3.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
|
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.1.0"
|
version = "2.3.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -148,11 +176,11 @@ provides-extras = ["dev"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "25.0"
|
version = "26.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -175,7 +203,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.4.1"
|
version = "9.0.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
@@ -186,139 +214,173 @@ dependencies = [
|
|||||||
{ name = "pygments" },
|
{ name = "pygments" },
|
||||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-cov"
|
name = "pytest-cov"
|
||||||
version = "6.2.1"
|
version = "7.0.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "coverage", extra = ["toml"] },
|
{ name = "coverage", extra = ["toml"] },
|
||||||
{ name = "pluggy" },
|
{ name = "pluggy" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" },
|
{ url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.2"
|
version = "6.0.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" },
|
{ url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" },
|
{ url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" },
|
{ url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" },
|
{ url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" },
|
{ url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" },
|
{ url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" },
|
{ url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" },
|
{ url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" },
|
{ url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" },
|
{ url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" },
|
{ url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" },
|
{ url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" },
|
{ url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" },
|
{ url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" },
|
{ url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" },
|
{ url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
|
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
|
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
|
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
|
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
|
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
|
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
|
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
|
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
|
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
|
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
|
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
|
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
|
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
|
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
|
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.12.9"
|
version = "0.15.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4a/45/2e403fa7007816b5fbb324cb4f8ed3c7402a927a0a0cb2b6279879a8bfdc/ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", size = 5254702, upload-time = "2025-08-14T16:08:55.2Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/ad/20/53bf098537adb7b6a97d98fcdebf6e916fcd11b2e21d15f8c171507909cc/ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e", size = 11759705, upload-time = "2025-08-14T16:08:12.968Z" },
|
{ url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/4d/c764ee423002aac1ec66b9d541285dd29d2c0640a8086c87de59ebbe80d5/ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", size = 12527042, upload-time = "2025-08-14T16:08:16.54Z" },
|
{ url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/45/cfcdf6d3eb5fc78a5b419e7e616d6ccba0013dc5b180522920af2897e1be/ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", size = 11724457, upload-time = "2025-08-14T16:08:18.686Z" },
|
{ url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/e6/44615c754b55662200c48bebb02196dbb14111b6e266ab071b7e7297b4ec/ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", size = 11949446, upload-time = "2025-08-14T16:08:21.059Z" },
|
{ url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/d1/9b7d46625d617c7df520d40d5ac6cdcdf20cbccb88fad4b5ecd476a6bb8d/ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", size = 11566350, upload-time = "2025-08-14T16:08:23.433Z" },
|
{ url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/20/b73132f66f2856bc29d2d263c6ca457f8476b0bbbe064dac3ac3337a270f/ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", size = 13270430, upload-time = "2025-08-14T16:08:25.837Z" },
|
{ url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/21/eaf3806f0a3d4c6be0a69d435646fba775b65f3f2097d54898b0fd4bb12e/ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", size = 14264717, upload-time = "2025-08-14T16:08:27.907Z" },
|
{ url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/82/1d0c53bd37dcb582b2c521d352fbf4876b1e28bc0d8894344198f6c9950d/ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", size = 13684331, upload-time = "2025-08-14T16:08:30.352Z" },
|
{ url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/2f/1c5cf6d8f656306d42a686f1e207f71d7cebdcbe7b2aa18e4e8a0cb74da3/ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", size = 12739151, upload-time = "2025-08-14T16:08:32.55Z" },
|
{ url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/47/09/25033198bff89b24d734e6479e39b1968e4c992e82262d61cdccaf11afb9/ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", size = 12954992, upload-time = "2025-08-14T16:08:34.816Z" },
|
{ url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/52/8e/d0dbf2f9dca66c2d7131feefc386523404014968cd6d22f057763935ab32/ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", size = 12899569, upload-time = "2025-08-14T16:08:36.852Z" },
|
{ url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/bd/b614d7c08515b1428ed4d3f1d4e3d687deffb2479703b90237682586fa66/ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", size = 11751983, upload-time = "2025-08-14T16:08:39.314Z" },
|
{ url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/d6/383e9f818a2441b1a0ed898d7875f11273f10882f997388b2b51cb2ae8b5/ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", size = 11538635, upload-time = "2025-08-14T16:08:41.297Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/9c/56f869d314edaa9fc1f491706d1d8a47747b9d714130368fbd69ce9024e9/ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", size = 12534346, upload-time = "2025-08-14T16:08:43.39Z" },
|
{ url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bd/4b/d8b95c6795a6c93b439bc913ee7a94fda42bb30a79285d47b80074003ee7/ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", size = 13017021, upload-time = "2025-08-14T16:08:45.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/c1/5f9a839a697ce1acd7af44836f7c2181cdae5accd17a5cb85fcbd694075e/ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", size = 11734785, upload-time = "2025-08-14T16:08:48.062Z" },
|
{ url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/66/cdddc2d1d9a9f677520b7cfc490d234336f523d4b429c1298de359a3be08/ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", size = 12840654, upload-time = "2025-08-14T16:08:50.158Z" },
|
{ url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ac/fd/669816bc6b5b93b9586f3c1d87cd6bc05028470b3ecfebb5938252c47a35/ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", size = 11949623, upload-time = "2025-08-14T16:08:52.233Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.2.1"
|
version = "2.4.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
|
{ url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
|
{ url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
|
{ url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
|
{ url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
|
{ url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
|
{ url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
|
{ url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
|
{ url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
|
{ url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
|
{ url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
|
{ url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
|
{ url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
|
{ url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
|
{ url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
|
{ url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
|
{ url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
|
{ url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
|
{ url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
|
{ url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
|
{ url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
|
{ url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
|
{ url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
|
{ url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
|
{ url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
|
{ url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.14.1"
|
version = "4.15.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -27,57 +27,45 @@ class CustomValidator(BaseValidator):
|
|||||||
self.boolean_validator = BooleanValidator()
|
self.boolean_validator = BooleanValidator()
|
||||||
self.file_validator = FileValidator()
|
self.file_validator = FileValidator()
|
||||||
|
|
||||||
def validate_inputs(self, inputs: dict[str, str]) -> bool: # pylint: disable=too-many-branches
|
def validate_inputs(self, inputs: dict[str, str]) -> bool:
|
||||||
"""Validate validate-inputs action inputs."""
|
"""Validate validate-inputs action inputs."""
|
||||||
valid = True
|
valid = True
|
||||||
|
|
||||||
# Validate action/action-type input
|
# Validate action/action-type input
|
||||||
if "action" in inputs or "action-type" in inputs:
|
action_key = self.get_key_variant(inputs, "action", "action-type")
|
||||||
action_input = inputs.get("action") or inputs.get("action-type", "")
|
if action_key:
|
||||||
# Check for empty action
|
action_input = inputs[action_key]
|
||||||
if action_input == "":
|
if action_input == "":
|
||||||
self.add_error("Action name cannot be empty")
|
self.add_error("Action name cannot be empty")
|
||||||
valid = False
|
valid = False
|
||||||
# Allow GitHub expressions
|
elif not self.is_github_expression(action_input):
|
||||||
elif action_input.startswith("${{") and action_input.endswith("}}"):
|
# Only validate non-GitHub expressions
|
||||||
pass # GitHub expressions are valid
|
if any(
|
||||||
# Check for dangerous characters
|
char in action_input
|
||||||
elif any(
|
for char in [";", "`", "$", "&", "|", ">", "<", "\n", "\r", "/"]
|
||||||
char in action_input
|
):
|
||||||
for char in [";", "`", "$", "&", "|", ">", "<", "\n", "\r", "/"]
|
self.add_error(f"Invalid characters in action name: {action_input}")
|
||||||
):
|
valid = False
|
||||||
self.add_error(f"Invalid characters in action name: {action_input}")
|
elif action_input and not re.match(r"^[a-z][a-z0-9_-]*[a-z0-9]$", action_input):
|
||||||
valid = False
|
self.add_error(f"Invalid action name format: {action_input}")
|
||||||
# Validate action name format (should be lowercase with hyphens or underscores)
|
valid = False
|
||||||
elif action_input and not re.match(r"^[a-z][a-z0-9_-]*[a-z0-9]$", action_input):
|
|
||||||
self.add_error(f"Invalid action name format: {action_input}")
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate rules-file if provided
|
# Validate rules-file if provided
|
||||||
if inputs.get("rules-file"):
|
if inputs.get("rules-file"):
|
||||||
result = self.file_validator.validate_file_path(inputs["rules-file"], "rules-file")
|
valid &= self.validate_with(
|
||||||
for error in self.file_validator.errors:
|
self.file_validator, "validate_file_path", inputs["rules-file"], "rules-file"
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.file_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
# Validate fail-on-error boolean
|
# Validate fail-on-error boolean
|
||||||
if "fail-on-error" in inputs:
|
if "fail-on-error" in inputs:
|
||||||
value = inputs["fail-on-error"]
|
value = inputs["fail-on-error"]
|
||||||
# Reject empty string
|
|
||||||
if value == "":
|
if value == "":
|
||||||
self.add_error("fail-on-error cannot be empty")
|
self.add_error("fail-on-error cannot be empty")
|
||||||
valid = False
|
valid = False
|
||||||
elif value:
|
elif value:
|
||||||
result = self.boolean_validator.validate_boolean(value, "fail-on-error")
|
valid &= self.validate_with(
|
||||||
for error in self.boolean_validator.errors:
|
self.boolean_validator, "validate_boolean", value, "fail-on-error"
|
||||||
if error not in self.errors:
|
)
|
||||||
self.add_error(error)
|
|
||||||
self.boolean_validator.clear_errors()
|
|
||||||
if not result:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
|||||||
@@ -8,56 +8,62 @@ Centralized Python-based input validation for GitHub Actions with PCRE regex sup
|
|||||||
|
|
||||||
### Inputs
|
### Inputs
|
||||||
|
|
||||||
| name | description | required | default |
|
| name | description | required | default |
|
||||||
|---------------------|-------------------------------------------------------------------------------------|----------|---------|
|
|----------------------|-------------------------------------------------------------------------------------|----------|---------|
|
||||||
| `action` | <p>Action name to validate (alias for action-type)</p> | `false` | `""` |
|
| `action` | <p>Action name to validate (alias for action-type)</p> | `false` | `""` |
|
||||||
| `action-type` | <p>Type of action to validate (e.g., csharp-publish, docker-build, eslint-lint)</p> | `false` | `""` |
|
| `action-type` | <p>Type of action to validate (e.g., csharp-publish, docker-build, eslint-lint)</p> | `false` | `""` |
|
||||||
| `rules-file` | <p>Path to validation rules file</p> | `false` | `""` |
|
| `rules-file` | <p>Path to validation rules file</p> | `false` | `""` |
|
||||||
| `fail-on-error` | <p>Whether to fail on validation errors</p> | `false` | `true` |
|
| `fail-on-error` | <p>Whether to fail on validation errors</p> | `false` | `true` |
|
||||||
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
|
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
|
||||||
| `namespace` | <p>Namespace/username for validation</p> | `false` | `""` |
|
| `namespace` | <p>Namespace/username for validation</p> | `false` | `""` |
|
||||||
| `email` | <p>Email address for validation</p> | `false` | `""` |
|
| `email` | <p>Email address for validation</p> | `false` | `""` |
|
||||||
| `username` | <p>Username for validation</p> | `false` | `""` |
|
| `username` | <p>Username for validation</p> | `false` | `""` |
|
||||||
| `dotnet-version` | <p>.NET version string</p> | `false` | `""` |
|
| `dotnet-version` | <p>.NET version string</p> | `false` | `""` |
|
||||||
| `terraform-version` | <p>Terraform version string</p> | `false` | `""` |
|
| `terraform-version` | <p>Terraform version string</p> | `false` | `""` |
|
||||||
| `tflint-version` | <p>TFLint version string</p> | `false` | `""` |
|
| `tflint-version` | <p>TFLint version string</p> | `false` | `""` |
|
||||||
| `node-version` | <p>Node.js version string</p> | `false` | `""` |
|
| `node-version` | <p>Node.js version string</p> | `false` | `""` |
|
||||||
| `force-version` | <p>Force version override</p> | `false` | `""` |
|
| `force-version` | <p>Force version override</p> | `false` | `""` |
|
||||||
| `default-version` | <p>Default version fallback</p> | `false` | `""` |
|
| `default-version` | <p>Default version fallback</p> | `false` | `""` |
|
||||||
| `image-name` | <p>Docker image name</p> | `false` | `""` |
|
| `image-name` | <p>Docker image name</p> | `false` | `""` |
|
||||||
| `tag` | <p>Docker image tag</p> | `false` | `""` |
|
| `tag` | <p>Docker image tag</p> | `false` | `""` |
|
||||||
| `architectures` | <p>Target architectures</p> | `false` | `""` |
|
| `architectures` | <p>Target architectures</p> | `false` | `""` |
|
||||||
| `dockerfile` | <p>Dockerfile path</p> | `false` | `""` |
|
| `dockerfile` | <p>Dockerfile path</p> | `false` | `""` |
|
||||||
| `context` | <p>Docker build context</p> | `false` | `""` |
|
| `context` | <p>Docker build context</p> | `false` | `""` |
|
||||||
| `build-args` | <p>Docker build arguments</p> | `false` | `""` |
|
| `build-args` | <p>Docker build arguments</p> | `false` | `""` |
|
||||||
| `buildx-version` | <p>Docker Buildx version</p> | `false` | `""` |
|
| `buildx-version` | <p>Docker Buildx version</p> | `false` | `""` |
|
||||||
| `max-retries` | <p>Maximum retry attempts</p> | `false` | `""` |
|
| `max-retries` | <p>Maximum retry attempts</p> | `false` | `""` |
|
||||||
| `image-quality` | <p>Image quality percentage</p> | `false` | `""` |
|
| `image-quality` | <p>Image quality percentage</p> | `false` | `""` |
|
||||||
| `png-quality` | <p>PNG quality percentage</p> | `false` | `""` |
|
| `png-quality` | <p>PNG quality percentage</p> | `false` | `""` |
|
||||||
| `parallel-builds` | <p>Number of parallel builds</p> | `false` | `""` |
|
| `parallel-builds` | <p>Number of parallel builds</p> | `false` | `""` |
|
||||||
| `days-before-stale` | <p>Number of days before marking as stale</p> | `false` | `""` |
|
| `days-before-stale` | <p>Number of days before marking as stale</p> | `false` | `""` |
|
||||||
| `days-before-close` | <p>Number of days before closing stale items</p> | `false` | `""` |
|
| `days-before-close` | <p>Number of days before closing stale items</p> | `false` | `""` |
|
||||||
| `pre-commit-config` | <p>Pre-commit configuration file path</p> | `false` | `""` |
|
| `pre-commit-config` | <p>Pre-commit configuration file path</p> | `false` | `""` |
|
||||||
| `base-branch` | <p>Base branch name</p> | `false` | `""` |
|
| `base-branch` | <p>Base branch name</p> | `false` | `""` |
|
||||||
| `dry-run` | <p>Dry run mode</p> | `false` | `""` |
|
| `dry-run` | <p>Dry run mode</p> | `false` | `""` |
|
||||||
| `is_fiximus` | <p>Use Fiximus bot</p> | `false` | `""` |
|
| `is_fiximus` | <p>Use Fiximus bot</p> | `false` | `""` |
|
||||||
| `prefix` | <p>Release tag prefix</p> | `false` | `""` |
|
| `prefix` | <p>Release tag prefix</p> | `false` | `""` |
|
||||||
| `language` | <p>Language to analyze (for CodeQL)</p> | `false` | `""` |
|
| `language` | <p>Language to analyze (for CodeQL)</p> | `false` | `""` |
|
||||||
| `queries` | <p>CodeQL queries to run</p> | `false` | `""` |
|
| `queries` | <p>CodeQL queries to run</p> | `false` | `""` |
|
||||||
| `packs` | <p>CodeQL query packs</p> | `false` | `""` |
|
| `packs` | <p>CodeQL query packs</p> | `false` | `""` |
|
||||||
| `config-file` | <p>CodeQL configuration file path</p> | `false` | `""` |
|
| `config-file` | <p>CodeQL configuration file path</p> | `false` | `""` |
|
||||||
| `config` | <p>CodeQL configuration YAML string</p> | `false` | `""` |
|
| `config` | <p>CodeQL configuration YAML string</p> | `false` | `""` |
|
||||||
| `build-mode` | <p>Build mode for compiled languages</p> | `false` | `""` |
|
| `build-mode` | <p>Build mode for compiled languages</p> | `false` | `""` |
|
||||||
| `source-root` | <p>Source code root directory</p> | `false` | `""` |
|
| `source-root` | <p>Source code root directory</p> | `false` | `""` |
|
||||||
| `category` | <p>Analysis category</p> | `false` | `""` |
|
| `category` | <p>Analysis category</p> | `false` | `""` |
|
||||||
| `checkout-ref` | <p>Git reference to checkout</p> | `false` | `""` |
|
| `checkout-ref` | <p>Git reference to checkout</p> | `false` | `""` |
|
||||||
| `working-directory` | <p>Working directory for analysis</p> | `false` | `""` |
|
| `working-directory` | <p>Working directory for analysis</p> | `false` | `""` |
|
||||||
| `upload-results` | <p>Upload results to GitHub Security</p> | `false` | `""` |
|
| `upload-results` | <p>Upload results to GitHub Security</p> | `false` | `""` |
|
||||||
| `ram` | <p>Memory in MB for CodeQL</p> | `false` | `""` |
|
| `ram` | <p>Memory in MB for CodeQL</p> | `false` | `""` |
|
||||||
| `threads` | <p>Number of threads for CodeQL</p> | `false` | `""` |
|
| `threads` | <p>Number of threads for CodeQL</p> | `false` | `""` |
|
||||||
| `output` | <p>Output path for SARIF results</p> | `false` | `""` |
|
| `output` | <p>Output path for SARIF results</p> | `false` | `""` |
|
||||||
| `skip-queries` | <p>Skip running queries</p> | `false` | `""` |
|
| `skip-queries` | <p>Skip running queries</p> | `false` | `""` |
|
||||||
| `add-snippets` | <p>Add code snippets to SARIF</p> | `false` | `""` |
|
| `add-snippets` | <p>Add code snippets to SARIF</p> | `false` | `""` |
|
||||||
|
| `gitleaks-license` | <p>Gitleaks license key</p> | `false` | `""` |
|
||||||
|
| `gitleaks-config` | <p>Gitleaks configuration file path</p> | `false` | `""` |
|
||||||
|
| `trivy-severity` | <p>Trivy severity levels to scan</p> | `false` | `""` |
|
||||||
|
| `trivy-scanners` | <p>Trivy scanner types to run</p> | `false` | `""` |
|
||||||
|
| `trivy-timeout` | <p>Trivy scan timeout</p> | `false` | `""` |
|
||||||
|
| `actionlint-enabled` | <p>Enable actionlint scanning</p> | `false` | `""` |
|
||||||
|
|
||||||
### Outputs
|
### Outputs
|
||||||
|
|
||||||
@@ -365,4 +371,40 @@ This action is a `composite` action.
|
|||||||
#
|
#
|
||||||
# Required: false
|
# Required: false
|
||||||
# Default: ""
|
# Default: ""
|
||||||
|
|
||||||
|
gitleaks-license:
|
||||||
|
# Gitleaks license key
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: ""
|
||||||
|
|
||||||
|
gitleaks-config:
|
||||||
|
# Gitleaks configuration file path
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: ""
|
||||||
|
|
||||||
|
trivy-severity:
|
||||||
|
# Trivy severity levels to scan
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: ""
|
||||||
|
|
||||||
|
trivy-scanners:
|
||||||
|
# Trivy scanner types to run
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: ""
|
||||||
|
|
||||||
|
trivy-timeout:
|
||||||
|
# Trivy scan timeout
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: ""
|
||||||
|
|
||||||
|
actionlint-enabled:
|
||||||
|
# Enable actionlint scanning
|
||||||
|
#
|
||||||
|
# Required: false
|
||||||
|
# Default: ""
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -173,6 +173,26 @@ inputs:
|
|||||||
description: 'Add code snippets to SARIF'
|
description: 'Add code snippets to SARIF'
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
# Security-scan specific inputs
|
||||||
|
gitleaks-license:
|
||||||
|
description: 'Gitleaks license key'
|
||||||
|
required: false
|
||||||
|
gitleaks-config:
|
||||||
|
description: 'Gitleaks configuration file path'
|
||||||
|
required: false
|
||||||
|
trivy-severity:
|
||||||
|
description: 'Trivy severity levels to scan'
|
||||||
|
required: false
|
||||||
|
trivy-scanners:
|
||||||
|
description: 'Trivy scanner types to run'
|
||||||
|
required: false
|
||||||
|
trivy-timeout:
|
||||||
|
description: 'Trivy scan timeout'
|
||||||
|
required: false
|
||||||
|
actionlint-enabled:
|
||||||
|
description: 'Enable actionlint scanning'
|
||||||
|
required: false
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
validation-status:
|
validation-status:
|
||||||
description: 'Overall validation status (success/failure)'
|
description: 'Overall validation status (success/failure)'
|
||||||
@@ -193,6 +213,10 @@ outputs:
|
|||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install Python dependencies
|
||||||
|
shell: bash
|
||||||
|
run: pip install pyyaml==6.0.3
|
||||||
|
|
||||||
- name: Validate Action Inputs with Python
|
- name: Validate Action Inputs with Python
|
||||||
id: validate
|
id: validate
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class ValidationRuleGenerator:
|
|||||||
"prefix": re.compile(r"\b(prefix|tag[_-]?prefix)\b", re.IGNORECASE),
|
"prefix": re.compile(r"\b(prefix|tag[_-]?prefix)\b", re.IGNORECASE),
|
||||||
# Boolean patterns (broad, should be lower priority)
|
# Boolean patterns (broad, should be lower priority)
|
||||||
"boolean": re.compile(
|
"boolean": re.compile(
|
||||||
r"\b(dry-?run|verbose|enable|disable|auto|skip|force|cache|provenance|sbom|scan|sign|fail[_-]?on[_-]?error|nightly)\b",
|
r"\b(dry-?run|verbose|enable|disable|auto|skip|force|cache|provenance|sbom|scan|sign|push|fail[_-]?on[_-]?error|nightly)\b",
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
),
|
),
|
||||||
# File extensions pattern
|
# File extensions pattern
|
||||||
@@ -160,36 +160,36 @@ class ValidationRuleGenerator:
|
|||||||
"npm_token": "github_token",
|
"npm_token": "github_token",
|
||||||
"password": "github_token",
|
"password": "github_token",
|
||||||
# Complex fields that should skip validation
|
# Complex fields that should skip validation
|
||||||
"build-args": None, # Can be empty
|
"build-args": "key_value_list", # Docker build arguments (KEY=VALUE format)
|
||||||
"context": None, # Default handled
|
"context": "file_path", # Build context path
|
||||||
"cache-from": None, # Complex cache syntax
|
"cache-from": "cache_config", # Docker cache configuration
|
||||||
"cache-export": None, # Complex cache syntax
|
"cache-export": "cache_config", # Docker cache configuration
|
||||||
"cache-import": None, # Complex cache syntax
|
"cache-import": "cache_config", # Docker cache configuration
|
||||||
"build-contexts": None, # Complex syntax
|
"build-contexts": "key_value_list", # Docker build contexts (KEY=VALUE format)
|
||||||
"secrets": None, # Complex syntax
|
"secrets": "key_value_list", # Docker secrets (KEY=VALUE format)
|
||||||
"platform-build-args": None, # JSON format
|
"platform-build-args": "json_format", # JSON format for platform-specific args
|
||||||
"extensions": None, # PHP extensions list
|
"extensions": "php_extensions", # PHP extensions list
|
||||||
"tools": None, # PHP tools list
|
"tools": "linter_list", # PHP tools list - same pattern as linters
|
||||||
|
"framework": "framework_mode", # PHP framework mode (auto, laravel, generic)
|
||||||
"args": None, # Composer args
|
"args": None, # Composer args
|
||||||
"stability": None, # Composer stability
|
"stability": None, # Composer stability
|
||||||
"registry-url": "url", # URL format
|
"registry-url": "url", # URL format
|
||||||
"scope": "scope", # NPM scope
|
"scope": "scope", # NPM scope
|
||||||
"plugins": None, # Prettier plugins
|
"plugins": "linter_list", # Prettier plugins - same pattern as linters
|
||||||
"file-extensions": "file_extensions", # File extension list
|
"file-extensions": "file_extensions", # File extension list
|
||||||
"file-pattern": None, # Glob pattern
|
"file-pattern": "path_list", # Glob pattern for file paths
|
||||||
"enable-linters": None, # Linter list
|
"enable-linters": "linter_list", # Linter list
|
||||||
"disable-linters": None, # Linter list
|
"disable-linters": "linter_list", # Linter list
|
||||||
"success-codes": None, # Exit code list
|
"success-codes": "exit_code_list", # Exit code list
|
||||||
"retry-codes": None, # Exit code list
|
"retry-codes": "exit_code_list", # Exit code list
|
||||||
"ignore-paths": None, # Path patterns
|
"ignore-paths": "path_list", # Path patterns to ignore
|
||||||
"key-files": None, # Cache key files
|
"key-files": "path_list", # Cache key files (paths)
|
||||||
"restore-keys": None, # Cache restore keys
|
"restore-keys": "path_list", # Cache restore keys (paths)
|
||||||
"env-vars": None, # Environment variables
|
"env-vars": "key_value_list", # Environment variables (KEY=VALUE format)
|
||||||
# Action-specific fields that need special handling
|
# Action-specific fields that need special handling
|
||||||
"type": None, # Cache type enum (npm, composer, go, etc.) - complex enum,
|
"type": None, # Cache type enum (npm, composer, go, etc.) - complex enum,
|
||||||
# skip validation
|
# skip validation
|
||||||
"paths": None, # File paths for caching (comma-separated) - complex format,
|
"paths": "path_list", # File paths for caching (comma-separated)
|
||||||
# skip validation
|
|
||||||
"command": None, # Shell command - complex format, skip validation for safety
|
"command": None, # Shell command - complex format, skip validation for safety
|
||||||
"backoff-strategy": None, # Retry strategy enum - complex enum, skip validation
|
"backoff-strategy": None, # Retry strategy enum - complex enum, skip validation
|
||||||
"shell": None, # Shell type enum - simple enum, skip validation
|
"shell": None, # Shell type enum - simple enum, skip validation
|
||||||
@@ -199,10 +199,13 @@ class ValidationRuleGenerator:
|
|||||||
"retry-delay": "numeric_range_1_300", # Retry delay should support higher values
|
"retry-delay": "numeric_range_1_300", # Retry delay should support higher values
|
||||||
"max-warnings": "numeric_range_0_10000",
|
"max-warnings": "numeric_range_0_10000",
|
||||||
# version-file-parser specific fields
|
# version-file-parser specific fields
|
||||||
"language": None, # Simple enum (node, php, python, go, dotnet)
|
|
||||||
"tool-versions-key": None, # Simple string (nodejs, python, php, golang, dotnet)
|
"tool-versions-key": None, # Simple string (nodejs, python, php, golang, dotnet)
|
||||||
"dockerfile-image": None, # Simple string (node, python, php, golang, dotnet)
|
"dockerfile-image": None, # Simple string (node, python, php, golang, dotnet)
|
||||||
"validation-regex": "regex_pattern", # Regex pattern - validate for ReDoS
|
"validation-regex": "regex_pattern", # Regex pattern - validate for ReDoS
|
||||||
|
# Docker network mode
|
||||||
|
"network": "network_mode", # Docker network mode (host, none, default)
|
||||||
|
# Language enum for version detection
|
||||||
|
"language": "language_enum", # Language type (php, python, go, dotnet)
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_action_directories(self) -> list[str]:
|
def get_action_directories(self) -> list[str]:
|
||||||
@@ -314,7 +317,6 @@ class ValidationRuleGenerator:
|
|||||||
"docker-publish": {
|
"docker-publish": {
|
||||||
"registry": "registry_enum",
|
"registry": "registry_enum",
|
||||||
"cache-mode": "cache_mode",
|
"cache-mode": "cache_mode",
|
||||||
"platforms": None, # Skip validation - complex platform format
|
|
||||||
},
|
},
|
||||||
"docker-publish-hub": {
|
"docker-publish-hub": {
|
||||||
"password": "docker_password",
|
"password": "docker_password",
|
||||||
@@ -354,26 +356,28 @@ class ValidationRuleGenerator:
|
|||||||
"prettier-lint": {
|
"prettier-lint": {
|
||||||
"mode": "mode_enum",
|
"mode": "mode_enum",
|
||||||
},
|
},
|
||||||
|
"security-scan": {
|
||||||
|
"gitleaks-config": "file_path",
|
||||||
|
"trivy-severity": "severity_enum",
|
||||||
|
"trivy-scanners": "scanner_list",
|
||||||
|
"trivy-timeout": "timeout_with_unit",
|
||||||
|
"actionlint-enabled": "boolean",
|
||||||
|
"token": "github_token",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if action_name in action_overrides:
|
if action_name in action_overrides:
|
||||||
# Apply overrides for existing conventions
|
# Apply overrides for existing conventions
|
||||||
overrides.update(
|
|
||||||
{
|
|
||||||
input_name: override_value
|
|
||||||
for input_name, override_value in action_overrides[action_name].items()
|
|
||||||
if input_name in conventions
|
|
||||||
},
|
|
||||||
)
|
|
||||||
# Add missing inputs from overrides to conventions
|
|
||||||
for input_name, override_value in action_overrides[action_name].items():
|
for input_name, override_value in action_overrides[action_name].items():
|
||||||
if input_name not in conventions and input_name in action_data["inputs"]:
|
if input_name in action_data["inputs"]:
|
||||||
|
overrides[input_name] = override_value
|
||||||
|
# Update conventions to match override (or set to None if skipped)
|
||||||
conventions[input_name] = override_value
|
conventions[input_name] = override_value
|
||||||
|
|
||||||
# Calculate statistics
|
# Calculate statistics
|
||||||
total_inputs = len(action_data["inputs"])
|
total_inputs = len(action_data["inputs"])
|
||||||
validated_inputs = len(conventions)
|
validated_inputs = sum(1 for v in conventions.values() if v is not None)
|
||||||
skipped_inputs = sum(1 for v in overrides.values() if v is None)
|
skipped_inputs = sum(1 for v in conventions.values() if v is None)
|
||||||
coverage = round((validated_inputs / total_inputs) * 100) if total_inputs > 0 else 0
|
coverage = round((validated_inputs / total_inputs) * 100) if total_inputs > 0 else 0
|
||||||
|
|
||||||
# Generate rules object with enhanced metadata
|
# Generate rules object with enhanced metadata
|
||||||
@@ -432,8 +436,20 @@ class ValidationRuleGenerator:
|
|||||||
|
|
||||||
# Use a custom yaml dumper to ensure proper indentation
|
# Use a custom yaml dumper to ensure proper indentation
|
||||||
class CustomYamlDumper(yaml.SafeDumper):
|
class CustomYamlDumper(yaml.SafeDumper):
|
||||||
def increase_indent(self, flow: bool = False, *, indentless: bool = False) -> None: # noqa: FBT001, FBT002
|
def increase_indent(self, flow: bool = False, *, indentless: bool = False) -> None: # noqa: FBT001, FBT002, ARG002 # type: ignore[override]
|
||||||
return super().increase_indent(flow, indentless=indentless)
|
return super().increase_indent(flow, False)
|
||||||
|
|
||||||
|
def choose_scalar_style(self):
|
||||||
|
"""Choose appropriate quote style based on string content."""
|
||||||
|
if hasattr(self, "event") and hasattr(self.event, "value") and self.event.value: # type: ignore[attr-defined]
|
||||||
|
value = self.event.value # type: ignore[attr-defined]
|
||||||
|
# Use literal block style for multiline strings
|
||||||
|
if "\n" in value:
|
||||||
|
return "|"
|
||||||
|
# Use double quotes for strings with single quotes
|
||||||
|
if "'" in value:
|
||||||
|
return '"'
|
||||||
|
return super().choose_scalar_style()
|
||||||
|
|
||||||
yaml_content = yaml.dump(
|
yaml_content = yaml.dump(
|
||||||
rules,
|
rules,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -274,6 +274,71 @@ class TestDockerValidator:
|
|||||||
result = self.validator.validate_inputs(inputs)
|
result = self.validator.validate_inputs(inputs)
|
||||||
assert isinstance(result, bool)
|
assert isinstance(result, bool)
|
||||||
|
|
||||||
|
def test_validate_registry_valid(self):
|
||||||
|
"""Test registry enum validation with valid values."""
|
||||||
|
valid_registries = [
|
||||||
|
"dockerhub",
|
||||||
|
"github",
|
||||||
|
"both",
|
||||||
|
]
|
||||||
|
|
||||||
|
for registry in valid_registries:
|
||||||
|
self.validator.errors = []
|
||||||
|
result = self.validator.validate_registry(registry)
|
||||||
|
assert result is True, f"Should accept registry: {registry}"
|
||||||
|
|
||||||
|
def test_validate_registry_invalid(self):
|
||||||
|
"""Test registry enum validation with invalid values."""
|
||||||
|
invalid_registries = [
|
||||||
|
"", # Empty
|
||||||
|
" ", # Whitespace only
|
||||||
|
"docker", # Wrong value (should be dockerhub)
|
||||||
|
"hub", # Wrong value
|
||||||
|
"ghcr", # Wrong value
|
||||||
|
"gcr", # Wrong value
|
||||||
|
"both,github", # Comma-separated not allowed
|
||||||
|
"DOCKERHUB", # Uppercase
|
||||||
|
"DockerHub", # Mixed case
|
||||||
|
"docker hub", # Space
|
||||||
|
"github.com", # Full URL not allowed
|
||||||
|
]
|
||||||
|
|
||||||
|
for registry in invalid_registries:
|
||||||
|
self.validator.errors = []
|
||||||
|
result = self.validator.validate_registry(registry)
|
||||||
|
assert result is False, f"Should reject registry: {registry}"
|
||||||
|
|
||||||
|
def test_validate_sbom_format_valid(self):
|
||||||
|
"""Test SBOM format validation with valid values."""
|
||||||
|
valid_formats = [
|
||||||
|
"spdx-json",
|
||||||
|
"cyclonedx-json",
|
||||||
|
"", # Empty is optional
|
||||||
|
]
|
||||||
|
|
||||||
|
for sbom_format in valid_formats:
|
||||||
|
self.validator.errors = []
|
||||||
|
result = self.validator.validate_sbom_format(sbom_format)
|
||||||
|
assert result is True, f"Should accept SBOM format: {sbom_format}"
|
||||||
|
|
||||||
|
def test_validate_sbom_format_invalid(self):
|
||||||
|
"""Test SBOM format validation with invalid values."""
|
||||||
|
invalid_formats = [
|
||||||
|
"spdx", # Missing -json suffix
|
||||||
|
"cyclonedx", # Missing -json suffix
|
||||||
|
"json", # Just json
|
||||||
|
"spdx-xml", # Wrong format
|
||||||
|
"cyclonedx-xml", # Wrong format
|
||||||
|
"SPDX-JSON", # Uppercase
|
||||||
|
"spdx json", # Space
|
||||||
|
"invalid", # Invalid value
|
||||||
|
]
|
||||||
|
|
||||||
|
for sbom_format in invalid_formats:
|
||||||
|
self.validator.errors = []
|
||||||
|
result = self.validator.validate_sbom_format(sbom_format)
|
||||||
|
assert result is False, f"Should reject SBOM format: {sbom_format}"
|
||||||
|
|
||||||
def test_empty_values_handling(self):
|
def test_empty_values_handling(self):
|
||||||
"""Test that empty values are handled appropriately."""
|
"""Test that empty values are handled appropriately."""
|
||||||
# Some Docker fields might be required, others optional
|
# Some Docker fields might be required, others optional
|
||||||
@@ -281,3 +346,5 @@ class TestDockerValidator:
|
|||||||
assert isinstance(self.validator.validate_docker_tag(""), bool)
|
assert isinstance(self.validator.validate_docker_tag(""), bool)
|
||||||
assert isinstance(self.validator.validate_architectures(""), bool)
|
assert isinstance(self.validator.validate_architectures(""), bool)
|
||||||
assert isinstance(self.validator.validate_prefix(""), bool)
|
assert isinstance(self.validator.validate_prefix(""), bool)
|
||||||
|
# Registry should reject empty values
|
||||||
|
assert self.validator.validate_registry("") is False
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class TestValidationRuleGenerator:
|
|||||||
generator = ValidationRuleGenerator()
|
generator = ValidationRuleGenerator()
|
||||||
|
|
||||||
# Test special cases from the mapping
|
# Test special cases from the mapping
|
||||||
assert generator.detect_validation_type("build-args", {}) is None
|
assert generator.detect_validation_type("build-args", {}) == "key_value_list"
|
||||||
assert generator.detect_validation_type("version", {}) == "flexible_version"
|
assert generator.detect_validation_type("version", {}) == "flexible_version"
|
||||||
assert (
|
assert (
|
||||||
generator.detect_validation_type("dotnet-version", {}) == "dotnet_version"
|
generator.detect_validation_type("dotnet-version", {}) == "dotnet_version"
|
||||||
|
|||||||
347
validate-inputs/uv.lock
generated
347
validate-inputs/uv.lock
generated
@@ -1,8 +1,9 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 3
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.9'",
|
"python_full_version >= '3.10'",
|
||||||
|
"python_full_version == '3.9.*'",
|
||||||
"python_full_version < '3.9'",
|
"python_full_version < '3.9'",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ name = "coverage"
|
|||||||
version = "7.10.7"
|
version = "7.10.7"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.9'",
|
"python_full_version == '3.9.*'",
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
@@ -218,20 +219,141 @@ wheels = [
|
|||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
toml = [
|
toml = [
|
||||||
{ name = "tomli", marker = "python_full_version >= '3.9' and python_full_version <= '3.11'" },
|
{ name = "tomli", marker = "python_full_version == '3.9.*'" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.13.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.10'",
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
toml = [
|
||||||
|
{ name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "exceptiongroup"
|
name = "exceptiongroup"
|
||||||
version = "1.3.0"
|
version = "1.3.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
|
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
|
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -245,7 +367,8 @@ dependencies = [
|
|||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
{ name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
{ name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
{ name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
|
||||||
|
{ name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
{ name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
{ name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
{ name = "pytest-cov", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
{ name = "pytest-cov", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
@@ -264,18 +387,34 @@ provides-extras = ["dev"]
|
|||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version == '3.9.*'",
|
||||||
|
"python_full_version < '3.9'",
|
||||||
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "iniconfig"
|
||||||
version = "25.0"
|
version = "2.3.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.10'",
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "26.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -295,7 +434,8 @@ name = "pluggy"
|
|||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.9'",
|
"python_full_version >= '3.10'",
|
||||||
|
"python_full_version == '3.9.*'",
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
@@ -321,7 +461,7 @@ resolution-markers = [
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" },
|
{ name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" },
|
||||||
{ name = "exceptiongroup", marker = "python_full_version < '3.9'" },
|
{ name = "exceptiongroup", marker = "python_full_version < '3.9'" },
|
||||||
{ name = "iniconfig", marker = "python_full_version < '3.9'" },
|
{ name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
{ name = "packaging", marker = "python_full_version < '3.9'" },
|
{ name = "packaging", marker = "python_full_version < '3.9'" },
|
||||||
{ name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
{ name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||||
{ name = "tomli", marker = "python_full_version < '3.9'" },
|
{ name = "tomli", marker = "python_full_version < '3.9'" },
|
||||||
@@ -336,22 +476,43 @@ name = "pytest"
|
|||||||
version = "8.4.2"
|
version = "8.4.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.9'",
|
"python_full_version == '3.9.*'",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "python_full_version >= '3.9' and sys_platform == 'win32'" },
|
{ name = "colorama", marker = "python_full_version == '3.9.*' and sys_platform == 'win32'" },
|
||||||
{ name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
|
{ name = "exceptiongroup", marker = "python_full_version == '3.9.*'" },
|
||||||
{ name = "iniconfig", marker = "python_full_version >= '3.9'" },
|
{ name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
|
||||||
{ name = "packaging", marker = "python_full_version >= '3.9'" },
|
{ name = "packaging", marker = "python_full_version == '3.9.*'" },
|
||||||
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
|
||||||
{ name = "pygments", marker = "python_full_version >= '3.9'" },
|
{ name = "pygments", marker = "python_full_version == '3.9.*'" },
|
||||||
{ name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
|
{ name = "tomli", marker = "python_full_version == '3.9.*'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.10'",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
|
||||||
|
{ name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
|
||||||
|
{ name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
|
{ name = "packaging", marker = "python_full_version >= '3.10'" },
|
||||||
|
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
|
{ name = "pygments", marker = "python_full_version >= '3.10'" },
|
||||||
|
{ name = "tomli", marker = "python_full_version == '3.10.*'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-cov"
|
name = "pytest-cov"
|
||||||
version = "5.0.0"
|
version = "5.0.0"
|
||||||
@@ -373,12 +534,15 @@ name = "pytest-cov"
|
|||||||
version = "7.0.0"
|
version = "7.0.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.9'",
|
"python_full_version >= '3.10'",
|
||||||
|
"python_full_version == '3.9.*'",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.9'" },
|
{ name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.9.*'" },
|
||||||
|
{ name = "coverage", version = "7.13.4", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" },
|
||||||
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
||||||
{ name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
{ name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
|
||||||
|
{ name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
@@ -467,77 +631,81 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.14.0"
|
version = "0.15.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/41/b9/9bd84453ed6dd04688de9b3f3a4146a1698e8faae2ceeccce4e14c67ae17/ruff-0.14.0.tar.gz", hash = "sha256:62ec8969b7510f77945df916de15da55311fade8d6050995ff7f680afe582c57", size = 5452071, upload-time = "2025-10-07T18:21:55.763Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/4e/79d463a5f80654e93fa653ebfb98e0becc3f0e7cf6219c9ddedf1e197072/ruff-0.14.0-py3-none-linux_armv6l.whl", hash = "sha256:58e15bffa7054299becf4bab8a1187062c6f8cafbe9f6e39e0d5aface455d6b3", size = 12494532, upload-time = "2025-10-07T18:21:00.373Z" },
|
{ url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8", size = 13160768, upload-time = "2025-10-07T18:21:04.73Z" },
|
{ url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8", size = 12363376, upload-time = "2025-10-07T18:21:07.833Z" },
|
{ url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/42/e2/1ffef5a1875add82416ff388fcb7ea8b22a53be67a638487937aea81af27/ruff-0.14.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba9a8925e90f861502f7d974cc60e18ca29c72bb0ee8bfeabb6ade35a3abde7", size = 12608055, upload-time = "2025-10-07T18:21:10.72Z" },
|
{ url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4a/32/986725199d7cee510d9f1dfdf95bf1efc5fa9dd714d0d85c1fb1f6be3bc3/ruff-0.14.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e41f785498bd200ffc276eb9e1570c019c1d907b07cfb081092c8ad51975bbe7", size = 12318544, upload-time = "2025-10-07T18:21:13.741Z" },
|
{ url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/ed/4969cefd53315164c94eaf4da7cfba1f267dc275b0abdd593d11c90829a3/ruff-0.14.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30a58c087aef4584c193aebf2700f0fbcfc1e77b89c7385e3139956fa90434e2", size = 14001280, upload-time = "2025-10-07T18:21:16.411Z" },
|
{ url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/ad/96c1fc9f8854c37681c9613d825925c7f24ca1acfc62a4eb3896b50bacd2/ruff-0.14.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f8d07350bc7af0a5ce8812b7d5c1a7293cf02476752f23fdfc500d24b79b783c", size = 15027286, upload-time = "2025-10-07T18:21:19.577Z" },
|
{ url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b3/00/1426978f97df4fe331074baf69615f579dc4e7c37bb4c6f57c2aad80c87f/ruff-0.14.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eec3bbbf3a7d5482b5c1f42d5fc972774d71d107d447919fca620b0be3e3b75e", size = 14451506, upload-time = "2025-10-07T18:21:22.779Z" },
|
{ url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/d5/9c1cea6e493c0cf0647674cca26b579ea9d2a213b74b5c195fbeb9678e15/ruff-0.14.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16b68e183a0e28e5c176d51004aaa40559e8f90065a10a559176713fcf435206", size = 13437384, upload-time = "2025-10-07T18:21:25.758Z" },
|
{ url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e", size = 13447976, upload-time = "2025-10-07T18:21:28.83Z" },
|
{ url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/c0/ac42f546d07e4f49f62332576cb845d45c67cf5610d1851254e341d563b6/ruff-0.14.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c958f66ab884b7873e72df38dcabee03d556a8f2ee1b8538ee1c2bbd619883dd", size = 13682850, upload-time = "2025-10-07T18:21:31.842Z" },
|
{ url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/c4/4b0c9bcadd45b4c29fe1af9c5d1dc0ca87b4021665dfbe1c4688d407aa20/ruff-0.14.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7eb0499a2e01f6e0c285afc5bac43ab380cbfc17cd43a2e1dd10ec97d6f2c42d", size = 12449825, upload-time = "2025-10-07T18:21:35.074Z" },
|
{ url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/a8/e2e76288e6c16540fa820d148d83e55f15e994d852485f221b9524514730/ruff-0.14.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c63b2d99fafa05efca0ab198fd48fa6030d57e4423df3f18e03aa62518c565f", size = 12272599, upload-time = "2025-10-07T18:21:38.08Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/14/e2815d8eff847391af632b22422b8207704222ff575dec8d044f9ab779b2/ruff-0.14.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:668fce701b7a222f3f5327f86909db2bbe99c30877c8001ff934c5413812ac02", size = 13193828, upload-time = "2025-10-07T18:21:41.216Z" },
|
{ url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/c6/61ccc2987cf0aecc588ff8f3212dea64840770e60d78f5606cd7dc34de32/ruff-0.14.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a86bf575e05cb68dcb34e4c7dfe1064d44d3f0c04bbc0491949092192b515296", size = 13628617, upload-time = "2025-10-07T18:21:44.04Z" },
|
{ url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/e6/03b882225a1b0627e75339b420883dc3c90707a8917d2284abef7a58d317/ruff-0.14.0-py3-none-win32.whl", hash = "sha256:7450a243d7125d1c032cb4b93d9625dea46c8c42b4f06c6b709baac168e10543", size = 12367872, upload-time = "2025-10-07T18:21:46.67Z" },
|
{ url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl", hash = "sha256:ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2", size = 13464628, upload-time = "2025-10-07T18:21:50.318Z" },
|
{ url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730", size = 12565142, upload-time = "2025-10-07T18:21:53.577Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.3.0"
|
version = "2.4.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
|
{ url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
|
{ url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
|
{ url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
|
{ url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
|
{ url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
|
{ url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
|
{ url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
|
{ url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
|
{ url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
|
{ url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
|
{ url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
|
{ url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
|
{ url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
|
{ url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
|
{ url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
|
{ url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
|
{ url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
|
{ url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
|
{ url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
|
{ url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
|
{ url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
|
{ url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
|
{ url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
|
{ url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
|
{ url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
|
{ url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
|
{ url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
|
{ url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
|
{ url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
|
{ url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
|
{ url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
|
{ url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
|
{ url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
|
{ url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
|
{ url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -557,7 +725,8 @@ name = "typing-extensions"
|
|||||||
version = "4.15.0"
|
version = "4.15.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.9'",
|
"python_full_version >= '3.10'",
|
||||||
|
"python_full_version == '3.9.*'",
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|||||||
@@ -227,3 +227,82 @@ class BaseValidator(ABC):
|
|||||||
or ("${{" in value and "}}" in value)
|
or ("${{" in value and "}}" in value)
|
||||||
or (value.strip().startswith("${{") and value.strip().endswith("}}"))
|
or (value.strip().startswith("${{") and value.strip().endswith("}}"))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def propagate_errors(self, validator: BaseValidator, result: bool) -> bool:
|
||||||
|
"""Copy errors from another validator and return result.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
validator: The validator to copy errors from
|
||||||
|
result: The validation result to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The result parameter unchanged
|
||||||
|
"""
|
||||||
|
for error in validator.errors:
|
||||||
|
if error not in self.errors:
|
||||||
|
self.add_error(error)
|
||||||
|
validator.clear_errors()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def validate_with(
|
||||||
|
self, validator: BaseValidator, method: str, *args: Any, **kwargs: Any
|
||||||
|
) -> bool:
|
||||||
|
"""Call validator method and propagate errors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
validator: The validator instance to use
|
||||||
|
method: The method name to call on the validator
|
||||||
|
*args: Positional arguments to pass to the method
|
||||||
|
**kwargs: Keyword arguments to pass to the method
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The validation result
|
||||||
|
"""
|
||||||
|
result = getattr(validator, method)(*args, **kwargs)
|
||||||
|
return self.propagate_errors(validator, result)
|
||||||
|
|
||||||
|
def validate_enum(
|
||||||
|
self,
|
||||||
|
value: str,
|
||||||
|
name: str,
|
||||||
|
valid_values: list[str],
|
||||||
|
*,
|
||||||
|
case_sensitive: bool = False,
|
||||||
|
) -> bool:
|
||||||
|
"""Validate value is one of allowed options.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The value to validate
|
||||||
|
name: The name of the input for error messages
|
||||||
|
valid_values: List of allowed values
|
||||||
|
case_sensitive: Whether comparison should be case sensitive
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if value is valid or empty/GitHub expression, False otherwise
|
||||||
|
"""
|
||||||
|
if not value or self.is_github_expression(value):
|
||||||
|
return True
|
||||||
|
check = value if case_sensitive else value.lower()
|
||||||
|
allowed = valid_values if case_sensitive else [v.lower() for v in valid_values]
|
||||||
|
if check not in allowed:
|
||||||
|
self.add_error(f"Invalid {name}: {value}. Must be one of: {', '.join(valid_values)}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_key_variant(inputs: dict[str, str], *variants: str) -> str | None:
|
||||||
|
"""Get first matching key variant from inputs.
|
||||||
|
|
||||||
|
Useful for inputs that may use underscore or hyphen variants.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
inputs: Dictionary of inputs to check
|
||||||
|
*variants: Key variants to search for in order
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The first matching key, or None if no match
|
||||||
|
"""
|
||||||
|
for key in variants:
|
||||||
|
if key in inputs:
|
||||||
|
return key
|
||||||
|
return None
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ This validator automatically applies validation based on input naming convention
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -424,7 +425,10 @@ class ConventionBasedValidator(BaseValidator):
|
|||||||
if error not in self.errors:
|
if error not in self.errors:
|
||||||
self.add_error(error)
|
self.add_error(error)
|
||||||
# Clear the module's errors after copying
|
# Clear the module's errors after copying
|
||||||
validator_module.errors = []
|
if hasattr(validator_module, "clear_errors"):
|
||||||
|
validator_module.clear_errors()
|
||||||
|
else:
|
||||||
|
validator_module.errors = []
|
||||||
|
|
||||||
return result
|
return result
|
||||||
# Method not found, skip validation
|
# Method not found, skip validation
|
||||||
@@ -556,13 +560,33 @@ class ConventionBasedValidator(BaseValidator):
|
|||||||
self._validator_modules["codeql"] = codeql.CodeQLValidator()
|
self._validator_modules["codeql"] = codeql.CodeQLValidator()
|
||||||
return self._validator_modules["codeql"], f"validate_{validator_type}"
|
return self._validator_modules["codeql"], f"validate_{validator_type}"
|
||||||
|
|
||||||
# PHP-specific validators
|
# Convention-based validators
|
||||||
if validator_type in ["php_extensions", "coverage_driver", "mode_enum"]:
|
if validator_type in [
|
||||||
# Return self for PHP-specific validation methods
|
"php_extensions",
|
||||||
|
"coverage_driver",
|
||||||
|
"mode_enum",
|
||||||
|
"binary_enum",
|
||||||
|
"multi_value_enum",
|
||||||
|
"report_format",
|
||||||
|
"format_enum",
|
||||||
|
"linter_list",
|
||||||
|
"timeout_with_unit",
|
||||||
|
"severity_enum",
|
||||||
|
"scanner_list",
|
||||||
|
"exit_code_list",
|
||||||
|
"key_value_list",
|
||||||
|
"path_list",
|
||||||
|
"network_mode",
|
||||||
|
"language_enum",
|
||||||
|
"framework_mode",
|
||||||
|
"json_format",
|
||||||
|
"cache_config",
|
||||||
|
]:
|
||||||
|
# Return self for validation methods implemented in this class
|
||||||
return self, f"_validate_{validator_type}"
|
return self, f"_validate_{validator_type}"
|
||||||
|
|
||||||
# Package manager and report format validators
|
# Package manager validators
|
||||||
if validator_type in ["package_manager_enum", "report_format"]:
|
if validator_type in ["package_manager_enum"]:
|
||||||
# These could be in a separate module, but for now we'll put them in file validator
|
# These could be in a separate module, but for now we'll put them in file validator
|
||||||
if "file" not in self._validator_modules:
|
if "file" not in self._validator_modules:
|
||||||
from . import file
|
from . import file
|
||||||
@@ -592,9 +616,103 @@ class ConventionBasedValidator(BaseValidator):
|
|||||||
# Default range
|
# Default range
|
||||||
return 0, 100
|
return 0, 100
|
||||||
|
|
||||||
|
def _validate_comma_separated_list(
|
||||||
|
self,
|
||||||
|
value: str,
|
||||||
|
input_name: str,
|
||||||
|
item_pattern: str | None = None,
|
||||||
|
valid_items: list | None = None,
|
||||||
|
check_injection: bool = False,
|
||||||
|
item_name: str = "item",
|
||||||
|
) -> bool:
|
||||||
|
"""Validate comma-separated list of items (generic validator).
|
||||||
|
|
||||||
|
This is a generic validator that can be used for any comma-separated list
|
||||||
|
with either pattern-based or enum-based validation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The comma-separated list value
|
||||||
|
input_name: The input name for error messages
|
||||||
|
item_pattern: Regex pattern each item must match
|
||||||
|
(default: alphanumeric+hyphens+underscores)
|
||||||
|
valid_items: Optional list of valid items for enum-style validation
|
||||||
|
check_injection: Whether to check for shell injection patterns
|
||||||
|
item_name: Descriptive name for items in error messages (e.g., "linter", "extension")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> # Pattern-based validation
|
||||||
|
>>> validator._validate_comma_separated_list(
|
||||||
|
... "gosec,govet", "enable-linters",
|
||||||
|
... item_pattern=r'^[a-zA-Z0-9_-]+$',
|
||||||
|
... item_name="linter"
|
||||||
|
... )
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> # Enum-based validation
|
||||||
|
>>> validator._validate_comma_separated_list(
|
||||||
|
... "vuln,config", "scanners",
|
||||||
|
... valid_items=["vuln", "config", "secret", "license"],
|
||||||
|
... item_name="scanner"
|
||||||
|
... )
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Security check for injection patterns
|
||||||
|
if check_injection and re.search(r"[;&|`$()]", value):
|
||||||
|
self.add_error(
|
||||||
|
f"Potential injection detected in {input_name}: {value}. "
|
||||||
|
f"Avoid using shell metacharacters (;, &, |, `, $, parentheses)"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Split by comma and validate each item
|
||||||
|
items = [item.strip() for item in value.split(",")]
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if not item: # Empty after strip
|
||||||
|
self.add_error(f"Invalid {input_name}: {value}. Contains empty {item_name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Enum-based validation (if valid_items provided)
|
||||||
|
if valid_items is not None:
|
||||||
|
if item not in valid_items:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {item_name} '{item}' in {input_name}. "
|
||||||
|
f"Must be one of: {', '.join(valid_items)}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Pattern-based validation (if no valid_items and pattern provided)
|
||||||
|
elif item_pattern is not None:
|
||||||
|
if not re.match(item_pattern, item):
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {item_name} '{item}' in {input_name}. "
|
||||||
|
f"Must match pattern: alphanumeric with hyphens/underscores"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Default pattern if neither valid_items nor item_pattern provided
|
||||||
|
elif not re.match(r"^[a-zA-Z0-9_-]+$", item):
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {item_name} '{item}' in {input_name}. "
|
||||||
|
f"Must be alphanumeric with hyphens/underscores"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _validate_php_extensions(self, value: str, input_name: str) -> bool:
|
def _validate_php_extensions(self, value: str, input_name: str) -> bool:
|
||||||
"""Validate PHP extensions format.
|
"""Validate PHP extensions format.
|
||||||
|
|
||||||
|
Wrapper for comma-separated list validator with PHP extension-specific rules.
|
||||||
|
Allows alphanumeric characters, underscores, and spaces.
|
||||||
|
Checks for shell injection patterns.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value: The extensions value (comma-separated list)
|
value: The extensions value (comma-separated list)
|
||||||
input_name: The input name for error messages
|
input_name: The input name for error messages
|
||||||
@@ -602,59 +720,727 @@ class ConventionBasedValidator(BaseValidator):
|
|||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
"""
|
||||||
import re
|
return self._validate_comma_separated_list(
|
||||||
|
value,
|
||||||
|
input_name,
|
||||||
|
item_pattern=r"^[a-zA-Z0-9_\s]+$",
|
||||||
|
check_injection=True,
|
||||||
|
item_name="extension",
|
||||||
|
)
|
||||||
|
|
||||||
if not value:
|
def _validate_binary_enum(
|
||||||
return True
|
self,
|
||||||
|
value: str,
|
||||||
|
input_name: str,
|
||||||
|
valid_values: list | None = None,
|
||||||
|
case_sensitive: bool = True,
|
||||||
|
) -> bool:
|
||||||
|
"""Validate binary enum (two-value choice) (generic validator).
|
||||||
|
|
||||||
# Check for injection patterns
|
This is a generic validator for two-value enums (e.g., check/fix, enabled/disabled).
|
||||||
if re.search(r"[;&|`$()@#]", value):
|
|
||||||
self.add_error(f"Potential injection detected in {input_name}: {value}")
|
Args:
|
||||||
|
value: The enum value
|
||||||
|
input_name: The input name for error messages
|
||||||
|
valid_values: List of exactly 2 valid values (default: ["check", "fix"])
|
||||||
|
case_sensitive: Whether validation is case-sensitive (default: True)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> # Default check/fix mode
|
||||||
|
>>> validator._validate_binary_enum("check", "mode")
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> # Custom binary enum
|
||||||
|
>>> validator._validate_binary_enum(
|
||||||
|
... "enabled", "status",
|
||||||
|
... valid_values=["enabled", "disabled"]
|
||||||
|
... )
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
if valid_values is None:
|
||||||
|
valid_values = ["check", "fix"]
|
||||||
|
|
||||||
|
if len(valid_values) != 2:
|
||||||
|
raise ValueError(
|
||||||
|
f"Binary enum requires exactly 2 valid values, got {len(valid_values)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Case-insensitive comparison if needed
|
||||||
|
if not case_sensitive:
|
||||||
|
value_lower = value.lower()
|
||||||
|
valid_values_lower = [v.lower() for v in valid_values]
|
||||||
|
if value_lower not in valid_values_lower:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_values)}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if value not in valid_values:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_values)}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _validate_format_enum(
|
||||||
|
self,
|
||||||
|
value: str,
|
||||||
|
input_name: str,
|
||||||
|
valid_formats: list | None = None,
|
||||||
|
allow_custom: bool = False,
|
||||||
|
) -> bool:
|
||||||
|
"""Validate output format enum (generic validator).
|
||||||
|
|
||||||
|
Generic validator for tool output formats (SARIF, JSON, XML, etc.).
|
||||||
|
Supports common formats across linting/analysis tools.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The format value
|
||||||
|
input_name: The input name for error messages
|
||||||
|
valid_formats: List of valid formats (default: comprehensive list)
|
||||||
|
allow_custom: Whether to allow formats not in the predefined list (default: False)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> # Default comprehensive format list
|
||||||
|
>>> validator._validate_format_enum("json", "format")
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> # Tool-specific format list
|
||||||
|
>>> validator._validate_format_enum(
|
||||||
|
... "sarif", "output-format",
|
||||||
|
... valid_formats=["json", "sarif", "text"]
|
||||||
|
... )
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
if valid_formats is None:
|
||||||
|
# Comprehensive list of common formats across all tools
|
||||||
|
valid_formats = [
|
||||||
|
"checkstyle",
|
||||||
|
"colored-line-number",
|
||||||
|
"compact",
|
||||||
|
"github-actions",
|
||||||
|
"html",
|
||||||
|
"json",
|
||||||
|
"junit",
|
||||||
|
"junit-xml",
|
||||||
|
"line-number",
|
||||||
|
"sarif",
|
||||||
|
"stylish",
|
||||||
|
"tab",
|
||||||
|
"teamcity",
|
||||||
|
"xml",
|
||||||
|
]
|
||||||
|
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Check if format is valid
|
||||||
|
if value not in valid_formats and not allow_custom:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_formats)}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check format - should be alphanumeric, underscores, commas, spaces only
|
return True
|
||||||
if not re.match(r"^[a-zA-Z0-9_,\s]+$", value):
|
|
||||||
self.add_error(f"Invalid format for {input_name}: {value}")
|
def _validate_multi_value_enum(
|
||||||
return False
|
self,
|
||||||
|
value: str,
|
||||||
|
input_name: str,
|
||||||
|
valid_values: list | None = None,
|
||||||
|
case_sensitive: bool = True,
|
||||||
|
min_values: int = 2,
|
||||||
|
max_values: int = 10,
|
||||||
|
) -> bool:
|
||||||
|
"""Validate multi-value enum (2-10 value choice) (generic validator).
|
||||||
|
|
||||||
|
Generic validator for enums with 2-10 predefined values.
|
||||||
|
For exactly 2 values, use _validate_binary_enum instead.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The enum value
|
||||||
|
input_name: The input name for error messages
|
||||||
|
valid_values: List of valid values (2-10 items required)
|
||||||
|
case_sensitive: Whether validation is case-sensitive (default: True)
|
||||||
|
min_values: Minimum number of valid values (default: 2)
|
||||||
|
max_values: Maximum number of valid values (default: 10)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> # Framework selection (3 values)
|
||||||
|
>>> validator._validate_multi_value_enum(
|
||||||
|
... "laravel", "framework",
|
||||||
|
... valid_values=["auto", "laravel", "generic"]
|
||||||
|
... )
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> # Language selection (4 values)
|
||||||
|
>>> validator._validate_multi_value_enum(
|
||||||
|
... "python", "language",
|
||||||
|
... valid_values=["php", "python", "go", "dotnet"]
|
||||||
|
... )
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
if valid_values is None:
|
||||||
|
raise ValueError("valid_values is required for multi_value_enum validator")
|
||||||
|
|
||||||
|
# Validate valid_values count
|
||||||
|
if len(valid_values) < min_values:
|
||||||
|
msg = f"Multi-value enum needs >= {min_values} values, got {len(valid_values)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if len(valid_values) > max_values:
|
||||||
|
msg = f"Multi-value enum allows <= {max_values} values, got {len(valid_values)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Case-insensitive comparison if needed
|
||||||
|
if not case_sensitive:
|
||||||
|
value_lower = value.lower()
|
||||||
|
valid_values_lower = [v.lower() for v in valid_values]
|
||||||
|
if value_lower not in valid_values_lower:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_values)}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if value not in valid_values:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_values)}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _validate_coverage_driver(self, value: str, input_name: str) -> bool:
|
def _validate_coverage_driver(self, value: str, input_name: str) -> bool:
|
||||||
"""Validate coverage driver enum.
|
"""Validate coverage driver enum.
|
||||||
|
|
||||||
|
Wrapper for multi_value_enum validator with PHP coverage driver options.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value: The coverage driver value
|
value: The coverage driver value
|
||||||
input_name: The input name for error messages
|
input_name: The input name for error messages
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "xdebug", "pcov", "xdebug3", "none", ""
|
||||||
|
Invalid: "xdebug2", "XDEBUG", "coverage"
|
||||||
"""
|
"""
|
||||||
valid_drivers = ["none", "xdebug", "pcov", "xdebug3"]
|
return self._validate_multi_value_enum(
|
||||||
|
value,
|
||||||
if value and value not in valid_drivers:
|
input_name,
|
||||||
self.add_error(
|
valid_values=["none", "xdebug", "pcov", "xdebug3"],
|
||||||
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_drivers)}"
|
case_sensitive=True,
|
||||||
)
|
)
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _validate_mode_enum(self, value: str, input_name: str) -> bool:
|
def _validate_mode_enum(self, value: str, input_name: str) -> bool:
|
||||||
"""Validate mode enum for linting actions.
|
"""Validate mode enum for linting actions.
|
||||||
|
|
||||||
|
Wrapper for binary_enum validator with check/fix modes.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value: The mode value
|
value: The mode value
|
||||||
input_name: The input name for error messages
|
input_name: The input name for error messages
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if valid, False otherwise
|
True if valid, False otherwise
|
||||||
"""
|
|
||||||
valid_modes = ["check", "fix"]
|
|
||||||
|
|
||||||
if value and value not in valid_modes:
|
Examples:
|
||||||
|
Valid: "check", "fix", ""
|
||||||
|
Invalid: "invalid", "CHECK", "Fix"
|
||||||
|
"""
|
||||||
|
return self._validate_binary_enum(
|
||||||
|
value,
|
||||||
|
input_name,
|
||||||
|
valid_values=["check", "fix"],
|
||||||
|
case_sensitive=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_report_format(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate report format for linting/analysis actions.
|
||||||
|
|
||||||
|
Wrapper for format_enum validator with comprehensive format list.
|
||||||
|
Supports multiple report formats used across different tools.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The report format value
|
||||||
|
input_name: The input name for error messages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "json", "sarif", "checkstyle", "github-actions", ""
|
||||||
|
Invalid: "invalid", "txt", "pdf"
|
||||||
|
"""
|
||||||
|
return self._validate_format_enum(value, input_name)
|
||||||
|
|
||||||
|
def _validate_linter_list(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate comma-separated list of linter names.
|
||||||
|
|
||||||
|
Wrapper for comma-separated list validator with linter-specific rules.
|
||||||
|
Allows alphanumeric characters, hyphens, and underscores.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The linter list value
|
||||||
|
input_name: The input name for error messages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "gosec,govet,staticcheck", "errcheck"
|
||||||
|
Invalid: "gosec,,govet", "invalid linter", "linter@123"
|
||||||
|
"""
|
||||||
|
return self._validate_comma_separated_list(
|
||||||
|
value,
|
||||||
|
input_name,
|
||||||
|
item_pattern=r"^[a-zA-Z0-9_-]+$",
|
||||||
|
item_name="linter",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_timeout_with_unit(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate timeout duration with unit (Go duration format).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The timeout value
|
||||||
|
input_name: The input name for error messages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Go duration format: number + unit (ns, us/µs, ms, s, m, h)
|
||||||
|
pattern = r"^[0-9]+(ns|us|µs|ms|s|m|h)$"
|
||||||
|
|
||||||
|
if not re.match(pattern, value):
|
||||||
self.add_error(
|
self.add_error(
|
||||||
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_modes)}"
|
f"Invalid {input_name}: {value}. Expected format: number with unit "
|
||||||
|
"(e.g., 5m, 30s, 1h, 500ms)"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _validate_severity_enum(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate severity levels enum (generalized).
|
||||||
|
|
||||||
|
Generic validator for security tool severity levels.
|
||||||
|
Supports common severity formats used by various security tools.
|
||||||
|
|
||||||
|
Default levels: UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL (Trivy/CVSSv3 style)
|
||||||
|
Case-sensitive by default.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The severity value (comma-separated for multiple levels)
|
||||||
|
input_name: The input name for error messages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Standard severity levels (Trivy/CVSSv3/OWASP compatible)
|
||||||
|
# Can be extended for specific tools by creating tool-specific validators
|
||||||
|
valid_severities = ["UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL"]
|
||||||
|
|
||||||
|
# Split by comma and validate each severity
|
||||||
|
severities = [s.strip() for s in value.split(",")]
|
||||||
|
|
||||||
|
for severity in severities:
|
||||||
|
if not severity: # Empty after strip
|
||||||
|
self.add_error(f"Invalid {input_name}: {value}. Contains empty severity level")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Case-sensitive validation
|
||||||
|
if severity not in valid_severities:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid {input_name}: {value}. Severity '{severity}' is not valid. "
|
||||||
|
f"Must be one of: {', '.join(valid_severities)}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _validate_scanner_list(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate comma-separated list of scanner types (for Trivy).
|
||||||
|
|
||||||
|
Wrapper for comma-separated list validator with Trivy scanner enum validation.
|
||||||
|
Supports: vuln, config, secret, license
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The scanner list value (comma-separated)
|
||||||
|
input_name: The input name for error messages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "vuln,config,secret", "vuln", "config,license"
|
||||||
|
Invalid: "invalid", "vuln,invalid,config", "vuln,,config"
|
||||||
|
"""
|
||||||
|
return self._validate_comma_separated_list(
|
||||||
|
value,
|
||||||
|
input_name,
|
||||||
|
valid_items=["vuln", "config", "secret", "license"],
|
||||||
|
item_name="scanner",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_exit_code_list(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate comma-separated list of exit codes.
|
||||||
|
|
||||||
|
Validates Unix/Linux exit codes (0-255) in comma-separated format.
|
||||||
|
Used for retry logic, success codes, and error handling.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The exit code list value (comma-separated integers)
|
||||||
|
input_name: The input name for error messages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "0", "0,1,2", "5,10,15", "0,130", ""
|
||||||
|
Invalid: "256", "0,256", "-1", "0,abc", "0,,1"
|
||||||
|
"""
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Split by comma and validate each exit code
|
||||||
|
codes = [code.strip() for code in value.split(",")]
|
||||||
|
|
||||||
|
for code in codes:
|
||||||
|
if not code: # Empty after strip
|
||||||
|
self.add_error(f"Invalid {input_name}: {value}. Contains empty exit code")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if code is numeric
|
||||||
|
if not re.match(r"^[0-9]+$", code):
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid exit code '{code}' in {input_name}. "
|
||||||
|
f"Exit codes must be integers (0-255)"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Validate range (0-255 for Unix/Linux exit codes)
|
||||||
|
code_int = int(code)
|
||||||
|
if code_int < 0 or code_int > 255:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid exit code '{code}' in {input_name}. Exit codes must be in range 0-255"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _validate_key_value_list(
|
||||||
|
self,
|
||||||
|
value: str,
|
||||||
|
input_name: str,
|
||||||
|
key_pattern: str | None = None,
|
||||||
|
check_injection: bool = True,
|
||||||
|
) -> bool:
|
||||||
|
"""Validate comma-separated list of key-value pairs (generic validator).
|
||||||
|
|
||||||
|
Validates KEY=VALUE,KEY2=VALUE2 format commonly used for Docker build-args,
|
||||||
|
environment variables, and other configuration parameters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The key-value list value (comma-separated KEY=VALUE pairs)
|
||||||
|
input_name: The input name for error messages
|
||||||
|
key_pattern: Regex pattern for key validation
|
||||||
|
(default: alphanumeric+underscores+hyphens)
|
||||||
|
check_injection: Whether to check for shell injection patterns
|
||||||
|
in values (default: True)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "KEY=value", "KEY1=value1,KEY2=value2", "BUILD_ARG=hello", ""
|
||||||
|
Invalid: "KEY", "=value", "KEY=", "KEY=value,", "KEY=val;whoami"
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
if key_pattern is None:
|
||||||
|
# Default: alphanumeric, underscores, hyphens (common for env vars and build args)
|
||||||
|
key_pattern = r"^[a-zA-Z0-9_-]+$"
|
||||||
|
|
||||||
|
# Security check for injection patterns in the entire value
|
||||||
|
if check_injection and re.search(r"[;&|`$()]", value):
|
||||||
|
self.add_error(
|
||||||
|
f"Potential injection detected in {input_name}: {value}. "
|
||||||
|
f"Avoid using shell metacharacters (;, &, |, `, $, parentheses)"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Split by comma and validate each key-value pair
|
||||||
|
pairs = [pair.strip() for pair in value.split(",")]
|
||||||
|
|
||||||
|
for pair in pairs:
|
||||||
|
if not pair: # Empty after strip
|
||||||
|
self.add_error(f"Invalid {input_name}: {value}. Contains empty key-value pair")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check for KEY=VALUE format
|
||||||
|
if "=" not in pair:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid key-value pair '{pair}' in {input_name}. Expected format: KEY=VALUE"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Split by first = only (value may contain =)
|
||||||
|
parts = pair.split("=", 1)
|
||||||
|
key = parts[0].strip()
|
||||||
|
|
||||||
|
# Validate key is not empty
|
||||||
|
if not key:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid key-value pair '{pair}' in {input_name}. Key cannot be empty"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Validate key pattern
|
||||||
|
if not re.match(key_pattern, key):
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid key '{key}' in {input_name}. "
|
||||||
|
f"Keys must be alphanumeric with underscores/hyphens"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Note: Value can be empty (KEY=) - this is valid for some use cases
|
||||||
|
# Value validation is optional and handled by the check_injection flag above
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _validate_path_list(
|
||||||
|
self,
|
||||||
|
value: str,
|
||||||
|
input_name: str,
|
||||||
|
allow_glob: bool = True,
|
||||||
|
check_injection: bool = True,
|
||||||
|
) -> bool:
|
||||||
|
"""Validate comma-separated list of file paths or glob patterns (generic validator).
|
||||||
|
|
||||||
|
Validates file paths and glob patterns commonly used for ignore-paths,
|
||||||
|
restore-keys, file-pattern, and other path-based inputs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The path list to validate
|
||||||
|
input_name: Name of the input being validated
|
||||||
|
allow_glob: Whether to allow glob patterns (*, **, ?, [])
|
||||||
|
check_injection: Whether to check for shell injection patterns
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "*.js", "src/**/*.ts", "dist/,build/", ".github/workflows/*", ""
|
||||||
|
Invalid: "../etc/passwd", "file;rm -rf /", "path|whoami"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Security check for injection patterns
|
||||||
|
if check_injection and re.search(r"[;&|`$()]", value):
|
||||||
|
self.add_error(
|
||||||
|
f"Potential injection detected in {input_name}: {value}. "
|
||||||
|
f"Avoid using shell metacharacters (;, &, |, `, $, parentheses)"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Split by comma and validate each path
|
||||||
|
paths = [path.strip() for path in value.split(",")]
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
if not path: # Empty after strip
|
||||||
|
self.add_error(f"Invalid {input_name}: {value}. Contains empty path")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check for path traversal attempts
|
||||||
|
if "../" in path or "/.." in path or path.startswith(".."):
|
||||||
|
self.add_error(
|
||||||
|
f"Path traversal detected in {input_name}: {path}. Avoid using '..' in paths"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Validate glob patterns if allowed
|
||||||
|
if allow_glob:
|
||||||
|
# Glob patterns are valid: *, **, ?, [], {}
|
||||||
|
# Check for valid glob characters
|
||||||
|
glob_pattern = r"^[a-zA-Z0-9_\-./\*\?\[\]\{\},@~+]+$"
|
||||||
|
if not re.match(glob_pattern, path):
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid path '{path}' in {input_name}. "
|
||||||
|
f"Paths may contain alphanumeric characters, hyphens, underscores, "
|
||||||
|
f"slashes, and glob patterns (*, **, ?, [], {{}})"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# No glob patterns allowed - only alphanumeric, hyphens, underscores, slashes
|
||||||
|
path_pattern = r"^[a-zA-Z0-9_\-./,@~+]+$"
|
||||||
|
if not re.match(path_pattern, path):
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid path '{path}' in {input_name}. "
|
||||||
|
f"Paths may only contain alphanumeric characters, hyphens, "
|
||||||
|
f"underscores, and slashes"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _validate_network_mode(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate Docker network mode enum.
|
||||||
|
|
||||||
|
Wrapper for multi_value_enum validator with Docker network mode options.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "host", "none", "default", ""
|
||||||
|
Invalid: "bridge", "NONE", "custom"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
return self._validate_multi_value_enum(
|
||||||
|
value,
|
||||||
|
input_name,
|
||||||
|
valid_values=["host", "none", "default"],
|
||||||
|
case_sensitive=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_language_enum(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate language enum for version detection.
|
||||||
|
|
||||||
|
Wrapper for multi_value_enum validator with supported language options.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "php", "python", "go", "dotnet", ""
|
||||||
|
Invalid: "node", "ruby", "PHP"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
return self._validate_multi_value_enum(
|
||||||
|
value,
|
||||||
|
input_name,
|
||||||
|
valid_values=["php", "python", "go", "dotnet"],
|
||||||
|
case_sensitive=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_framework_mode(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate PHP framework detection mode.
|
||||||
|
|
||||||
|
Wrapper for multi_value_enum validator with framework mode options.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "auto", "laravel", "generic", ""
|
||||||
|
Invalid: "symfony", "Auto", "LARAVEL"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
return self._validate_multi_value_enum(
|
||||||
|
value,
|
||||||
|
input_name,
|
||||||
|
valid_values=["auto", "laravel", "generic"],
|
||||||
|
case_sensitive=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_json_format(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate JSON format string.
|
||||||
|
|
||||||
|
Validates that input is valid JSON. Used for structured configuration
|
||||||
|
data like platform-specific build arguments.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: '{"key":"value"}', '[]', '{"platforms":["linux/amd64"]}', ""
|
||||||
|
Invalid: '{invalid}', 'not json', '{key:value}'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
try:
|
||||||
|
json.loads(value)
|
||||||
|
return True
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
self.add_error(f"Invalid JSON format in {input_name}: {value}. Error: {str(e)}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self.add_error(f"Failed to validate JSON in {input_name}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _validate_cache_config(self, value: str, input_name: str) -> bool:
|
||||||
|
"""Validate Docker BuildKit cache configuration.
|
||||||
|
|
||||||
|
Validates Docker cache export/import configuration format.
|
||||||
|
Common formats: type=registry,ref=..., type=local,dest=..., type=gha
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Valid: "type=registry,ref=user/repo:cache", "type=local,dest=/tmp/cache",
|
||||||
|
"type=gha", "type=inline", ""
|
||||||
|
Invalid: "invalid", "type=", "registry", "type=unknown"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
if not value or value.strip() == "":
|
||||||
|
return True # Optional
|
||||||
|
|
||||||
|
# Check basic format: type=value[,key=value,...]
|
||||||
|
if not re.match(r"^type=[a-z0-9-]+", value):
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid cache config in {input_name}: {value}. "
|
||||||
|
f"Must start with 'type=<cache-type>'"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Valid cache types
|
||||||
|
valid_types = ["registry", "local", "gha", "inline", "s3", "azblob", "oci"]
|
||||||
|
|
||||||
|
# Extract type
|
||||||
|
type_match = re.match(r"^type=([a-z0-9-]+)", value)
|
||||||
|
if type_match:
|
||||||
|
cache_type = type_match.group(1)
|
||||||
|
if cache_type not in valid_types:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid cache type '{cache_type}' in {input_name}. "
|
||||||
|
f"Valid types: {', '.join(valid_types)}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Validate key=value pairs format
|
||||||
|
parts = value.split(",")
|
||||||
|
for part in parts:
|
||||||
|
if "=" not in part:
|
||||||
|
self.add_error(
|
||||||
|
f"Invalid cache config format in {input_name}: {value}. "
|
||||||
|
f"Each part must be in 'key=value' format"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class TokenValidator(BaseValidator):
|
|||||||
"""Validator for various authentication tokens."""
|
"""Validator for various authentication tokens."""
|
||||||
|
|
||||||
# Token patterns for different token types (based on official GitHub documentation)
|
# Token patterns for different token types (based on official GitHub documentation)
|
||||||
# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats
|
# See: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/
|
||||||
|
# about-authentication-to-github#githubs-token-formats
|
||||||
# Note: The lengths include the prefix
|
# Note: The lengths include the prefix
|
||||||
TOKEN_PATTERNS: ClassVar[dict[str, str]] = {
|
TOKEN_PATTERNS: ClassVar[dict[str, str]] = {
|
||||||
# Personal access token (classic):
|
# Personal access token (classic):
|
||||||
|
|||||||
Reference in New Issue
Block a user