mirror of
https://github.com/alirezarezvani/ClaudeForge.git
synced 2026-07-03 10:23:15 -04:00
feat(ci): implement comprehensive CI/CD workflows and quality gates
Phase 1: Core GitHub Workflows Implementation Composite Actions (4): - setup-python-deps: Cache Python dependencies for faster runs - fork-safety: Detect fork PRs and prevent malicious write operations - rate-limit-check: Circuit breaker pattern for GitHub API exhaustion - quality-gates: Python syntax, Markdown lint, Bash validation, secret scanning Workflows (5): - bootstrap.yml: One-time repository setup (labels, milestones, settings) - reusable-pr-checks.yml: DRY quality gate orchestrator - pr-into-dev.yml: Feature PR validation (branch names, conventional commits, linked issues) - dev-to-main.yml: Release gate validation (source branch, CHANGELOG, production readiness) - release.yml: Manual release creation with GitHub releases and auto-generated notes Branch Strategy: Standard (feature/* → dev → main) Quality Gates: Python, Markdown, Bash, Secrets Release Trigger: Manual via /release command or workflow_dispatch Implements comprehensive CI/CD system adapted from blueprint: - Fork safety and rate limiting for security - Conventional commits enforcement - Automated quality validation - Production release gates - GitHub release automation Next: Phase 2 (templates, CODEOWNERS, dependabot)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
name: 'Fork Safety Check'
|
||||
description: 'Detect fork PRs to skip write operations and maintain security'
|
||||
author: 'ClaudeForge'
|
||||
|
||||
branding:
|
||||
icon: 'shield'
|
||||
color: 'blue'
|
||||
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'GitHub token for API access (usually secrets.GITHUB_TOKEN)'
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
|
||||
outputs:
|
||||
is-fork:
|
||||
description: 'Boolean indicating if the PR is from a fork (true/false)'
|
||||
value: ${{ steps.check-fork.outputs.is-fork }}
|
||||
should-skip-writes:
|
||||
description: 'Boolean indicating if write operations should be skipped (true/false)'
|
||||
value: ${{ steps.check-fork.outputs.should-skip-writes }}
|
||||
source-repo:
|
||||
description: 'Full name of the source repository (owner/repo)'
|
||||
value: ${{ steps.check-fork.outputs.source-repo }}
|
||||
base-repo:
|
||||
description: 'Full name of the base repository (owner/repo)'
|
||||
value: ${{ steps.check-fork.outputs.base-repo }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Check if PR is from fork
|
||||
id: check-fork
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||||
run: |
|
||||
echo "::group::Fork Safety Check"
|
||||
|
||||
# Initialize outputs
|
||||
IS_FORK="false"
|
||||
SHOULD_SKIP_WRITES="false"
|
||||
SOURCE_REPO="unknown"
|
||||
BASE_REPO="unknown"
|
||||
|
||||
# Check if this is a pull request event
|
||||
if [[ "${{ github.event_name }}" == "pull_request"* ]]; then
|
||||
echo "📋 Event: Pull Request detected"
|
||||
|
||||
# Get fork status from event context
|
||||
FORK_STATUS="${{ github.event.pull_request.head.repo.fork }}"
|
||||
SOURCE_REPO="${{ github.event.pull_request.head.repo.full_name }}"
|
||||
BASE_REPO="${{ github.event.pull_request.base.repo.full_name }}"
|
||||
|
||||
echo "🔍 Source Repository: $SOURCE_REPO"
|
||||
echo "🎯 Base Repository: $BASE_REPO"
|
||||
|
||||
if [[ "$FORK_STATUS" == "true" ]]; then
|
||||
IS_FORK="true"
|
||||
SHOULD_SKIP_WRITES="true"
|
||||
echo "⚠️ Fork PR detected - Write operations should be skipped"
|
||||
echo "🔒 Security: Preventing potential malicious actions from forked PR"
|
||||
else
|
||||
echo "✅ Same-repository PR - Write operations allowed"
|
||||
fi
|
||||
|
||||
else
|
||||
echo "ℹ️ Not a pull request event - treating as safe (non-fork)"
|
||||
echo "📌 Event type: ${{ github.event_name }}"
|
||||
fi
|
||||
|
||||
# Set outputs
|
||||
echo "is-fork=$IS_FORK" >> $GITHUB_OUTPUT
|
||||
echo "should-skip-writes=$SHOULD_SKIP_WRITES" >> $GITHUB_OUTPUT
|
||||
echo "source-repo=$SOURCE_REPO" >> $GITHUB_OUTPUT
|
||||
echo "base-repo=$BASE_REPO" >> $GITHUB_OUTPUT
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "📊 Fork Safety Check Results:"
|
||||
echo " - Is Fork: $IS_FORK"
|
||||
echo " - Skip Writes: $SHOULD_SKIP_WRITES"
|
||||
echo " - Source: $SOURCE_REPO"
|
||||
echo " - Base: $BASE_REPO"
|
||||
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Log fork detection result
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ steps.check-fork.outputs.is-fork }}" == "true" ]]; then
|
||||
echo "::warning::This PR is from a fork. Write operations will be skipped for security."
|
||||
fi
|
||||
@@ -0,0 +1,278 @@
|
||||
name: 'ClaudeForge Quality Gates'
|
||||
description: 'Comprehensive quality validation for Python, Markdown, Bash scripts, and security'
|
||||
author: 'ClaudeForge'
|
||||
|
||||
branding:
|
||||
icon: 'check-circle'
|
||||
color: 'green'
|
||||
|
||||
inputs:
|
||||
python-version:
|
||||
description: 'Python version to use for validation'
|
||||
required: false
|
||||
default: '3.11'
|
||||
skip-python:
|
||||
description: 'Skip Python validation'
|
||||
required: false
|
||||
default: 'false'
|
||||
skip-markdown:
|
||||
description: 'Skip Markdown validation'
|
||||
required: false
|
||||
default: 'false'
|
||||
skip-bash:
|
||||
description: 'Skip Bash script validation'
|
||||
required: false
|
||||
default: 'false'
|
||||
skip-secrets:
|
||||
description: 'Skip secret scanning'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
python-passed:
|
||||
description: 'Whether Python validation passed'
|
||||
value: ${{ steps.validate-python.outputs.passed }}
|
||||
markdown-passed:
|
||||
description: 'Whether Markdown validation passed'
|
||||
value: ${{ steps.validate-markdown.outputs.passed }}
|
||||
bash-passed:
|
||||
description: 'Whether Bash validation passed'
|
||||
value: ${{ steps.validate-bash.outputs.passed }}
|
||||
secrets-passed:
|
||||
description: 'Whether secret scanning passed'
|
||||
value: ${{ steps.scan-secrets.outputs.passed }}
|
||||
all-passed:
|
||||
description: 'Whether all quality gates passed'
|
||||
value: ${{ steps.summary.outputs.all-passed }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup Python for validation
|
||||
if: inputs.skip-python != 'true'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- name: Install validation tools
|
||||
if: inputs.skip-python != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
pip install flake8 pylint mypy black --quiet
|
||||
|
||||
- name: Validate Python syntax and style
|
||||
id: validate-python
|
||||
if: inputs.skip-python != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::Python Validation"
|
||||
PASSED="true"
|
||||
|
||||
# Find all Python files
|
||||
PYTHON_FILES=$(find skill -name "*.py" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$PYTHON_FILES" ]; then
|
||||
echo "ℹ️ No Python files found to validate"
|
||||
echo "passed=true" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "📋 Found Python files:"
|
||||
echo "$PYTHON_FILES"
|
||||
echo ""
|
||||
|
||||
# Run flake8 (syntax and style)
|
||||
echo "🔍 Running flake8 (syntax and style)..."
|
||||
if flake8 skill/ --count --select=E9,F63,F7,F82 --show-source --statistics; then
|
||||
echo "✅ Flake8 syntax check passed"
|
||||
else
|
||||
echo "::error::Flake8 found syntax errors"
|
||||
PASSED="false"
|
||||
fi
|
||||
|
||||
# Run flake8 for style (non-blocking)
|
||||
echo ""
|
||||
echo "🎨 Running flake8 (style warnings)..."
|
||||
flake8 skill/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics || true
|
||||
|
||||
echo "passed=$PASSED" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
|
||||
if [ "$PASSED" = "false" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Validate Markdown files
|
||||
id: validate-markdown
|
||||
if: inputs.skip-markdown != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::Markdown Validation"
|
||||
PASSED="true"
|
||||
|
||||
# Find all Markdown files
|
||||
MD_FILES=$(find . -name "*.md" -not -path "./node_modules/*" -not -path "./.git/*" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$MD_FILES" ]; then
|
||||
echo "ℹ️ No Markdown files found to validate"
|
||||
echo "passed=true" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "📋 Found Markdown files: $(echo "$MD_FILES" | wc -l) files"
|
||||
echo ""
|
||||
|
||||
# Basic validation checks
|
||||
echo "🔍 Checking Markdown files..."
|
||||
|
||||
for file in $MD_FILES; do
|
||||
# Check for empty files
|
||||
if [ ! -s "$file" ]; then
|
||||
echo "::warning file=$file::Empty Markdown file"
|
||||
fi
|
||||
|
||||
# Check for broken relative links (basic check)
|
||||
BROKEN_LINKS=$(grep -o '\[.*\]([^h].*\.md)' "$file" | sed 's/.*(\(.*\))/\1/' || true)
|
||||
for link in $BROKEN_LINKS; do
|
||||
LINK_PATH=$(dirname "$file")/"$link"
|
||||
if [ ! -f "$LINK_PATH" ]; then
|
||||
echo "::warning file=$file::Potentially broken link: $link"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo "✅ Markdown validation completed"
|
||||
echo "passed=$PASSED" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Validate Bash scripts
|
||||
id: validate-bash
|
||||
if: inputs.skip-bash != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::Bash Script Validation"
|
||||
PASSED="true"
|
||||
|
||||
# Find bash scripts
|
||||
BASH_FILES=$(find . -name "*.sh" -not -path "./node_modules/*" -not -path "./.git/*" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$BASH_FILES" ]; then
|
||||
echo "ℹ️ No Bash scripts found to validate"
|
||||
echo "passed=true" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "📋 Found Bash scripts:"
|
||||
echo "$BASH_FILES"
|
||||
echo ""
|
||||
|
||||
# Validate syntax
|
||||
echo "🔍 Checking Bash syntax..."
|
||||
for script in $BASH_FILES; do
|
||||
echo "Checking: $script"
|
||||
if bash -n "$script" 2>&1 | grep -v "warning:"; then
|
||||
echo " ✅ Syntax valid"
|
||||
else
|
||||
echo "::error file=$script::Bash syntax error detected"
|
||||
PASSED="false"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "passed=$PASSED" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
|
||||
if [ "$PASSED" = "false" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Scan for secrets
|
||||
id: scan-secrets
|
||||
if: inputs.skip-secrets != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::Secret Scanning"
|
||||
PASSED="true"
|
||||
|
||||
echo "🔍 Scanning for hardcoded secrets..."
|
||||
|
||||
# Patterns to detect
|
||||
PATTERNS=(
|
||||
"api[_-]?key"
|
||||
"api[_-]?secret"
|
||||
"password\s*="
|
||||
"token\s*="
|
||||
"secret\s*="
|
||||
"AWS_ACCESS_KEY"
|
||||
"AWS_SECRET_KEY"
|
||||
"GITHUB_TOKEN"
|
||||
"ANTHROPIC_API_KEY"
|
||||
)
|
||||
|
||||
# Files to scan (exclude binary, vendor, etc.)
|
||||
FILES=$(find . -type f \( -name "*.py" -o -name "*.sh" -o -name "*.md" -o -name "*.yml" -o -name "*.yaml" \) \
|
||||
-not -path "./node_modules/*" \
|
||||
-not -path "./.git/*" \
|
||||
-not -path "./venv/*" \
|
||||
2>/dev/null || echo "")
|
||||
|
||||
for pattern in "${PATTERNS[@]}"; do
|
||||
MATCHES=$(echo "$FILES" | xargs grep -niE "$pattern" 2>/dev/null || true)
|
||||
|
||||
if [ ! -z "$MATCHES" ]; then
|
||||
# Filter out common false positives (examples, docs, variable declarations without values)
|
||||
REAL_MATCHES=$(echo "$MATCHES" | grep -viE "(example|sample|placeholder|your_|xxx|<|template)" || true)
|
||||
|
||||
if [ ! -z "$REAL_MATCHES" ]; then
|
||||
echo "::warning::Potential secret found with pattern: $pattern"
|
||||
echo "$REAL_MATCHES"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for .env files committed
|
||||
if find . -name ".env" -not -path "./.git/*" | grep -q ".env"; then
|
||||
echo "::error::.env file found in repository. This should be in .gitignore!"
|
||||
PASSED="false"
|
||||
fi
|
||||
|
||||
if [ "$PASSED" = "true" ]; then
|
||||
echo "✅ No obvious secrets detected"
|
||||
fi
|
||||
|
||||
echo "passed=$PASSED" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Quality Gates Summary
|
||||
id: summary
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::Quality Gates Summary"
|
||||
|
||||
PYTHON_PASSED="${{ steps.validate-python.outputs.passed || 'true' }}"
|
||||
MARKDOWN_PASSED="${{ steps.validate-markdown.outputs.passed || 'true' }}"
|
||||
BASH_PASSED="${{ steps.validate-bash.outputs.passed || 'true' }}"
|
||||
SECRETS_PASSED="${{ steps.scan-secrets.outputs.passed || 'true' }}"
|
||||
|
||||
ALL_PASSED="true"
|
||||
|
||||
echo "📊 Quality Gates Results:"
|
||||
echo " - Python: $PYTHON_PASSED"
|
||||
echo " - Markdown: $MARKDOWN_PASSED"
|
||||
echo " - Bash: $BASH_PASSED"
|
||||
echo " - Secrets: $SECRETS_PASSED"
|
||||
echo ""
|
||||
|
||||
if [[ "$PYTHON_PASSED" == "false" ]] || [[ "$MARKDOWN_PASSED" == "false" ]] || \
|
||||
[[ "$BASH_PASSED" == "false" ]] || [[ "$SECRETS_PASSED" == "false" ]]; then
|
||||
ALL_PASSED="false"
|
||||
echo "❌ Some quality gates failed"
|
||||
else
|
||||
echo "✅ All quality gates passed"
|
||||
fi
|
||||
|
||||
echo "all-passed=$ALL_PASSED" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
@@ -0,0 +1,138 @@
|
||||
name: 'Rate Limit Check'
|
||||
description: 'Circuit breaker to prevent GitHub API exhaustion'
|
||||
author: 'ClaudeForge'
|
||||
|
||||
branding:
|
||||
icon: 'activity'
|
||||
color: 'orange'
|
||||
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'GitHub token for API access (usually secrets.GITHUB_TOKEN)'
|
||||
required: true
|
||||
minimum-remaining:
|
||||
description: 'Minimum API calls remaining to proceed (default: 50)'
|
||||
required: false
|
||||
default: '50'
|
||||
fail-on-limit:
|
||||
description: 'Whether to fail the workflow if below threshold (default: false, just warns)'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
can-proceed:
|
||||
description: 'Boolean indicating if enough API calls remain (true/false)'
|
||||
value: ${{ steps.check-limit.outputs.can-proceed }}
|
||||
remaining:
|
||||
description: 'Number of API calls remaining in current window'
|
||||
value: ${{ steps.check-limit.outputs.remaining }}
|
||||
limit:
|
||||
description: 'Total API rate limit for the token'
|
||||
value: ${{ steps.check-limit.outputs.limit }}
|
||||
used:
|
||||
description: 'Number of API calls used in current window'
|
||||
value: ${{ steps.check-limit.outputs.used }}
|
||||
reset-time:
|
||||
description: 'Unix timestamp when rate limit resets'
|
||||
value: ${{ steps.check-limit.outputs.reset-time }}
|
||||
reset-time-human:
|
||||
description: 'Human-readable time when rate limit resets'
|
||||
value: ${{ steps.check-limit.outputs.reset-time-human }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Check GitHub API Rate Limit
|
||||
id: check-limit
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||||
MIN_REMAINING: ${{ inputs.minimum-remaining }}
|
||||
FAIL_ON_LIMIT: ${{ inputs.fail-on-limit }}
|
||||
run: |
|
||||
echo "::group::GitHub API Rate Limit Check"
|
||||
|
||||
# Query rate limit status
|
||||
echo "🔍 Querying GitHub API rate limit status..."
|
||||
RATE_LIMIT_JSON=$(gh api rate_limit)
|
||||
|
||||
# Extract core API rate limit info
|
||||
REMAINING=$(echo "$RATE_LIMIT_JSON" | jq -r '.resources.core.remaining')
|
||||
LIMIT=$(echo "$RATE_LIMIT_JSON" | jq -r '.resources.core.limit')
|
||||
USED=$(echo "$RATE_LIMIT_JSON" | jq -r '.resources.core.used')
|
||||
RESET_TIMESTAMP=$(echo "$RATE_LIMIT_JSON" | jq -r '.resources.core.reset')
|
||||
|
||||
# Convert reset timestamp to human-readable format
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS date command
|
||||
RESET_TIME_HUMAN=$(date -r "$RESET_TIMESTAMP" '+%Y-%m-%d %H:%M:%S %Z')
|
||||
else
|
||||
# Linux date command
|
||||
RESET_TIME_HUMAN=$(date -d "@$RESET_TIMESTAMP" '+%Y-%m-%d %H:%M:%S %Z')
|
||||
fi
|
||||
|
||||
# Calculate percentage used
|
||||
PERCENT_USED=$(echo "scale=1; ($USED * 100) / $LIMIT" | bc)
|
||||
|
||||
# Determine if we can proceed
|
||||
CAN_PROCEED="false"
|
||||
if [[ $REMAINING -ge $MIN_REMAINING ]]; then
|
||||
CAN_PROCEED="true"
|
||||
fi
|
||||
|
||||
# Set outputs
|
||||
echo "can-proceed=$CAN_PROCEED" >> $GITHUB_OUTPUT
|
||||
echo "remaining=$REMAINING" >> $GITHUB_OUTPUT
|
||||
echo "limit=$LIMIT" >> $GITHUB_OUTPUT
|
||||
echo "used=$USED" >> $GITHUB_OUTPUT
|
||||
echo "reset-time=$RESET_TIMESTAMP" >> $GITHUB_OUTPUT
|
||||
echo "reset-time-human=$RESET_TIME_HUMAN" >> $GITHUB_OUTPUT
|
||||
|
||||
# Display results
|
||||
echo ""
|
||||
echo "📊 Rate Limit Status:"
|
||||
echo " - Limit: $LIMIT"
|
||||
echo " - Used: $USED ($PERCENT_USED%)"
|
||||
echo " - Remaining: $REMAINING"
|
||||
echo " - Minimum Required: $MIN_REMAINING"
|
||||
echo " - Reset Time: $RESET_TIME_HUMAN"
|
||||
echo ""
|
||||
|
||||
# Status indicators
|
||||
if [[ $CAN_PROCEED == "true" ]]; then
|
||||
echo "✅ Sufficient API calls remaining ($REMAINING >= $MIN_REMAINING)"
|
||||
else
|
||||
echo "⚠️ Rate limit too low ($REMAINING < $MIN_REMAINING)"
|
||||
echo "🕐 API limit will reset at: $RESET_TIME_HUMAN"
|
||||
|
||||
# Calculate wait time
|
||||
CURRENT_TIME=$(date +%s)
|
||||
WAIT_SECONDS=$((RESET_TIMESTAMP - CURRENT_TIME))
|
||||
WAIT_MINUTES=$((WAIT_SECONDS / 60))
|
||||
|
||||
if [[ $WAIT_SECONDS -gt 0 ]]; then
|
||||
echo "⏱️ Wait time: ~$WAIT_MINUTES minutes"
|
||||
else
|
||||
echo "⏱️ Rate limit should reset momentarily"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "::endgroup::"
|
||||
|
||||
# Handle warnings and failures
|
||||
if [[ $CAN_PROCEED == "false" ]]; then
|
||||
if [[ $FAIL_ON_LIMIT == "true" ]]; then
|
||||
echo "::error::GitHub API rate limit too low ($REMAINING remaining, need $MIN_REMAINING). Failing workflow."
|
||||
exit 1
|
||||
else
|
||||
echo "::warning::GitHub API rate limit too low ($REMAINING remaining, need $MIN_REMAINING). Workflow may fail or be rate-limited."
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Rate Limit Summary
|
||||
shell: bash
|
||||
if: steps.check-limit.outputs.can-proceed == 'false'
|
||||
run: |
|
||||
echo "::notice::⚠️ Low API Rate Limit Detected"
|
||||
echo "::notice::Remaining: ${{ steps.check-limit.outputs.remaining }} / ${{ steps.check-limit.outputs.limit }}"
|
||||
echo "::notice::Resets at: ${{ steps.check-limit.outputs.reset-time-human }}"
|
||||
@@ -0,0 +1,62 @@
|
||||
name: 'Setup Python Dependencies'
|
||||
description: 'Sets up Python with caching for faster workflow runs'
|
||||
author: 'ClaudeForge'
|
||||
|
||||
inputs:
|
||||
python-version:
|
||||
description: 'Python version to use'
|
||||
required: false
|
||||
default: '3.11'
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: 'Whether the cache was hit'
|
||||
value: ${{ steps.cache-pip.outputs.cache-hit }}
|
||||
python-version:
|
||||
description: 'Python version that was installed'
|
||||
value: ${{ steps.setup-python.outputs.python-version }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
shell: bash
|
||||
run: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pip dependencies
|
||||
id: cache-pip
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-pip-${{ inputs.python-version }}-${{ hashFiles('skill/requirements.txt', '**/setup.py', '**/pyproject.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-${{ inputs.python-version }}-
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Install Python dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
# Install common validation tools
|
||||
pip install flake8 pylint black mypy
|
||||
# Install project dependencies if they exist
|
||||
if [ -f "skill/requirements.txt" ]; then
|
||||
pip install -r skill/requirements.txt
|
||||
fi
|
||||
if [ -f "requirements.txt" ]; then
|
||||
pip install -r requirements.txt
|
||||
fi
|
||||
|
||||
- name: Display Python info
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Python version: $(python --version)"
|
||||
echo "Pip version: $(pip --version)"
|
||||
echo "Cache hit: ${{ steps.cache-pip.outputs.cache-hit }}"
|
||||
Reference in New Issue
Block a user