Skip to content

Feature/dpsn plugin #205

Feature/dpsn plugin

Feature/dpsn plugin #205

name: Validate New Plugin Metadata
on:
pull_request:
branches: [ "main" ]
paths: [ "plugins/**" ]
types: [ opened, edited, reopened, synchronize ]
jobs:
identify-new-plugins:
runs-on: ubuntu-latest
outputs:
plugin_dirs: ${{ steps.find_new_plugins.outputs.plugin_dirs }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Identify New Plugin Directories
id: find_new_plugins
run: |
# Fetch latest base branch state
git fetch origin ${{ github.event.pull_request.base.ref }}
BASE_COMMIT=$(git merge-base origin/${{ github.event.pull_request.base.ref }} HEAD)
# Find newly added plugin directories
NEW_PLUGINS=()
for plugin_dir in $(git diff --diff-filter=A --name-only $BASE_COMMIT...HEAD | grep '^plugins/' | cut -d'/' -f1-2 | sort -u); do
# Ensure directory is completely new (does not exist in base branch)
if ! git rev-parse --verify origin/${{ github.event.pull_request.base.ref }}:"$plugin_dir" &>/dev/null; then
NEW_PLUGINS+=("$plugin_dir")
fi
done
# Exit early if no new plugins were found
if [[ ${#NEW_PLUGINS[@]} -eq 0 ]]; then
echo "plugin_dirs=[]" >> $GITHUB_OUTPUT
exit 0
fi
# Convert plugin directory list to JSON format for use in next job
echo "plugin_dirs=$(jq -nc --argjson arr "$(printf '%s\n' "${NEW_PLUGINS[@]}" | jq -R . | jq -s .)" '$arr')" >> $GITHUB_OUTPUT
validate-individual-plugins:
needs: identify-new-plugins
if: ${{ needs.identify-new-plugins.outputs.plugin_dirs != '[]' }}
runs-on: ubuntu-latest
strategy:
matrix:
plugin_dir: ${{ fromJson(needs.identify-new-plugins.outputs.plugin_dirs) }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Validate Plugin Metadata
run: |
set +e # Disable exit on error to allow all fields to be validated
metadata_file="${{ matrix.plugin_dir }}/plugin_metadata.yml"
if [[ ! -f "$metadata_file" ]]; then
echo "::error file=$metadata_file::Missing plugin_metadata.yml"
exit 1
fi
echo "::group::Validating $metadata_file"
metadata=$(yq '.' "$metadata_file")
errors=0
# Regex pattern for a valid URL
url_regex='^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:[0-9]{1,5})?(\/.*)?$'
email_regex='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
yyyy_mm_regex='^202[0-9]{1}-[0-9]{2}$'
x_account_handle_regex='^@[a-zA-Z0-9_]{1,15}$'
# Function to print missing field error
missing_field_error() {
local field="$1"
echo "::error file=$metadata_file::'$field' is required but missing"
((errors++))
}
# Function to check if a required field is missing
check_required_field() {
local field="$1"
local value=$(echo "$metadata" | yq -r ".${field}")
if [[ -z "$value" || "$value" == "null" ]]; then
missing_field_error "$field"
fi
}
# Function to validate a URL field (supports single values and arrays, optional by default, add "required" as second parameter to make it required, ie: check_valid_url "community_url" "required")
check_valid_url() {
local field="$1"
local required="${2:-optional}" # Default to "optional" if not specified
local value
local field_type
local non_empty_value=0
value=$(echo "$metadata" | yq -r ".${field}" 2>/dev/null || echo "")
field_type=$(echo "$metadata" | yq -r ".${field} | type" 2>/dev/null || echo "")
# If field is missing or empty
if [[ "$value" == "null" || -z "$value" ]]; then
if [[ "$required" == "required" ]]; then
missing_field_error "$field"
fi
return 0 # Skip validation if optional
fi
# Handle arrays of URLs
if [[ "$field_type" == "!!seq" ]]; then
mapfile -t urls < <(echo "$metadata" | yq -r ".${field} | .[]")
for url in "${urls[@]}"; do
[[ -z "$url" ]] && continue
if [[ ! "$url" =~ $url_regex ]]; then
echo "::error file=$metadata_file::'$field' contains an invalid URL: $url"
((errors++))
else
((non_empty_value++))
fi
done
if [[ $non_empty_value -eq 0 && "$required" == "required" ]]; then
echo "::error file=$metadata_file::'$field' is required but missing valid values"
((errors++))
fi
else
# Single value validation
if [[ ! "$value" =~ $url_regex ]]; then
echo "::error file=$metadata_file::'$field' is not a valid URL: $value"
((errors++))
fi
fi
}
# Validate required fields
check_required_field "plugin_name"
check_required_field "author"
check_required_field "short_description"
check_required_field "detailed_description"
# Validate URLs (optional fields but must be valid if provided, add "required" as second parameter to make them required, ie: check_valid_url "community_url" "required")
check_valid_url "logo_url"
check_valid_url "plugin_logo_url"
check_valid_url "demo_video_url"
check_valid_url "documentation_url"
check_valid_url "changelog_url"
check_valid_url "community_url"
check_valid_url "screenshots"
# Validate date format (YYYY-MM)
release_date=$(echo "$metadata" | yq -r '.release_date')
if [[ -z "$release_date" || "$release_date" == "null" ]]; then
missing_field_error "release_date"
else
if [[ ! "$release_date" =~ $yyyy_mm_regex ]]; then
echo "::error file=$metadata_file::'release_date' should be in YYYY-MM format"
((errors++))
fi
fi
# Validate X account handle format (@username)
x_account_handle=$(echo "$metadata" | yq '.x_account_handle')
if [[ -z "$x_account_handle" || "$x_account_handle" == "null" ]]; then
missing_field_error "x_account_handle"
else
if [[ -n "$x_account_handle" && ! "$x_account_handle" =~ $x_account_handle_regex ]]; then
echo "::error file=$metadata_file::'x_account_handle' is not a valid X (Twitter) handle"
((errors++))
fi
fi
# Validate support contact (must be a valid URL or email)
support_contact=$(echo "$metadata" | yq '.support_contact')
if [[ -z "$support_contact" || "$support_contact" == "null" ]]; then
missing_field_error "support_contact"
else
if [[ ! "$support_contact" =~ ($url_regex|$email_regex) ]]; then
echo "::error file=$metadata_file::'support_contact' must be a valid URL or email address"
((errors++))
fi
fi
echo "::endgroup::"
exit $errors