Feature/dpsn plugin #205
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |