goose/.github/workflows/validate-recipe-pr.yml

265 lines
No EOL
12 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: Validate Recipe PR
on:
pull_request_target:
types: [opened, synchronize, reopened]
paths:
- 'documentation/src/pages/recipes/data/recipes/**'
permissions:
contents: read
pull-requests: write
issues: write
jobs:
validate-recipe:
runs-on: ubuntu-latest
env:
PROVIDER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install and Configure goose
run: |
mkdir -p /home/runner/.local/bin
curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh \
| CONFIGURE=false INSTALL_PATH=/home/runner/.local/bin bash
echo "/home/runner/.local/bin" >> $GITHUB_PATH
mkdir -p ~/.config/goose
cat <<EOF > ~/.config/goose/config.yaml
GOOSE_PROVIDER: openrouter
GOOSE_MODEL: "anthropic/claude-sonnet-4"
keyring: false
EOF
- name: Check if recipe files changed in this push
id: recipe_changes
run: |
set -e
echo "🔍 Checking if recipe files were modified in this push..."
# Get the list of changed files in this specific push (added/modified only, not deleted)
if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.action }}" = "synchronize" ]; then
# For synchronize events, check files changed since the previous commit
echo "📝 Synchronize event - checking files changed since previous commit"
CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.before }}..${{ github.event.after }})
else
# For opened/reopened, check all files in the PR (compare PR head against base)
echo "📝 PR opened/reopened - checking all files in PR"
CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
fi
echo "Changed files in this push:"
echo "$CHANGED_FILES"
echo ""
# Check if any recipe files were changed
if echo "$CHANGED_FILES" | grep -q "^documentation/src/pages/recipes/data/recipes/"; then
echo "recipe_files_changed=true" >> "$GITHUB_OUTPUT"
echo "✅ Recipe files were modified in this push - proceeding with validation"
else
echo "recipe_files_changed=false" >> "$GITHUB_OUTPUT"
echo " No recipe files were modified in this push - skipping validation"
fi
- name: Find recipe files in PR (new or modified)
id: find_changed_recipes
if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
run: |
set -e
echo "Looking for recipe files in PR (new or modified)..."
# Get the list of changed/new files in this PR (added/modified only, not deleted)
if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.action }}" = "synchronize" ]; then
# For synchronize events, check files changed since the previous commit
echo "📝 Synchronize event - checking files changed/added since previous commit"
CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.before }}..${{ github.event.after }})
else
# For opened/reopened, check all files in the PR (compare PR head against base)
echo "📝 PR opened/reopened - checking all new/modified files in PR"
CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
fi
# Filter for recipe files only that were changed or added
RECIPE_FILES=$(echo "$CHANGED_FILES" | grep "^documentation/src/pages/recipes/data/recipes/" | grep -E "\.(yaml|yml)$" || true)
if [ -z "$RECIPE_FILES" ]; then
echo "No changed recipe files found in PR"
echo "validation_status=no_files" >> $GITHUB_OUTPUT
exit 1
fi
echo "Found changed recipe files:"
echo "$RECIPE_FILES"
# Save recipe file paths for validation step
echo "$RECIPE_FILES" > /tmp/changed_recipe_files.txt
- name: Validate changed recipe files
id: validate
if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
run: |
set -e
# Read the list of changed recipe files
RECIPE_FILES=$(cat /tmp/changed_recipe_files.txt)
ALL_VALID=true
VALIDATION_OUTPUT=""
# First pass: Basic YAML validation
while IFS= read -r RECIPE_FILE; do
if [ -f "$RECIPE_FILE" ]; then
BASE_RECIPE_FILENAME=$(basename "$RECIPE_FILE")
echo "🔍 Validating: $BASE_RECIPE_FILENAME"
if OUTPUT=$(goose recipe validate "$RECIPE_FILE" 2>&1); then
echo "✅ Valid: $BASE_RECIPE_FILENAME"
VALIDATION_OUTPUT="${VALIDATION_OUTPUT}✅ $BASE_RECIPE_FILENAME: VALID\n"
else
echo "❌ Invalid: $BASE_RECIPE_FILENAME"
echo "$OUTPUT"
VALIDATION_OUTPUT="${VALIDATION_OUTPUT}❌ $BASE_RECIPE_FILENAME: INVALID\n\`\`\`\n$OUTPUT\n\`\`\`\n"
ALL_VALID=false
fi
fi
done < /tmp/changed_recipe_files.txt
# Second pass: Check for duplicate filenames
if [ "$ALL_VALID" = true ]; then
echo "🔍 Checking for duplicate filenames..."
# Check for duplicate filenames first
SEEN_FILENAMES=""
while IFS= read -r RECIPE_FILE; do
if [ -f "$RECIPE_FILE" ]; then
FILENAME=$(basename "$RECIPE_FILE" .yaml)
FILENAME=$(basename "$FILENAME" .yml)
echo "📋 Checking filename: '$FILENAME'"
# Check if we've seen this filename before in this PR
if echo "$SEEN_FILENAMES" | grep -q "^$FILENAME$"; then
echo "❌ Duplicate filename '$FILENAME' found in this PR"
VALIDATION_OUTPUT="${VALIDATION_OUTPUT}❌ Duplicate filename '$FILENAME' found in this PR\n"
ALL_VALID=false
else
SEEN_FILENAMES="$SEEN_FILENAMES\n$FILENAME"
fi
# Check if this is a new file or an update to existing file
# Get list of changed files in this PR compared to base branch
CHANGED_FILES=$(git diff --name-only --diff-filter=AM ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | grep "^$RECIPE_FILE$" || true)
EXISTING_FILES=$(find documentation/src/pages/recipes/data/recipes/ -name "$FILENAME.yaml" -o -name "$FILENAME.yml" | grep -v "^$RECIPE_FILE$" || true)
if [ -n "$EXISTING_FILES" ] && [ -z "$CHANGED_FILES" ]; then
# File exists in repo but is not being modified - this is a new duplicate
echo "❌ Recipe filename '$FILENAME' already exists:"
echo "$EXISTING_FILES"
VALIDATION_OUTPUT="${VALIDATION_OUTPUT}❌ $RECIPE_FILE: Filename '$FILENAME' already exists in: $EXISTING_FILES\n"
ALL_VALID=false
elif [ -n "$EXISTING_FILES" ] && [ -n "$CHANGED_FILES" ]; then
# File exists and is being modified - this is an update
echo "✅ Updating existing recipe: '$FILENAME'"
else
# File doesn't exist - this is a new recipe
echo "✅ New recipe filename '$FILENAME' is unique"
fi
echo "✅ Filename '$FILENAME' validation complete"
fi
done < /tmp/changed_recipe_files.txt
fi
# Save validation output for use in comment
echo "$VALIDATION_OUTPUT" > /tmp/validation_output.txt
if [ "$ALL_VALID" = true ]; then
echo "validation_status=valid" >> $GITHUB_OUTPUT
else
echo "validation_status=invalid" >> $GITHUB_OUTPUT
fi
- name: Comment validation results
if: steps.recipe_changes.outputs.recipe_files_changed == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const status = '${{ steps.validate.outputs.validation_status }}';
let comment;
if (status === 'no_files') {
comment = `❌ **Recipe Validation Failed**
No recipe files found in the correct location!
📁 **Please add your recipe to**: \`documentation/src/pages/recipes/data/recipes/your-recipe-id.yaml\`
**Example**: If your recipe ID is \`web-scraper\`, create:
\`documentation/src/pages/recipes/data/recipes/web-scraper.yaml\``;
} else if (status === 'valid') {
comment = `✅ **Recipe Validation Passed**
Your recipe(s) are valid and ready for review!
🔍 **Next Steps**:
1. Our team will review your recipe
2. If approved, we'll run a security scan
3. Once merged, you'll receive $10 in OpenRouter credits (if email provided)
Thanks for contributing to the goose Recipe Cookbook! 🎉`;
} else {
// Read validation details from file
let validationDetails = '';
try {
validationDetails = fs.readFileSync('/tmp/validation_output.txt', 'utf8');
} catch (e) {
validationDetails = 'See workflow logs for details.';
}
comment = `❌ **Recipe Validation Failed**
Please fix the validation errors and push your changes:
${validationDetails}
📚 Check our [Recipe Guide](https://block.github.io/goose/recipes) for help with the correct format.`;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: comment
});
- name: Set validation status
if: always()
run: |
# Check if recipe files were changed in this PR
if [ "${{ steps.recipe_changes.outputs.recipe_files_changed }}" = "false" ]; then
# No recipe files were modified in this PR - validation skipped
echo " No recipe files in PR - validation skipped"
exit 0
fi
VALIDATION_STATUS="${{ steps.validate.outputs.validation_status }}"
if [ "$VALIDATION_STATUS" = "valid" ]; then
echo "✅ All recipes are valid"
exit 0
else
echo "❌ Recipe validation failed"
exit 1
fi