diff --git a/.github/PULL_REQUEST_TEMPLATE/plugin.md b/.github/PULL_REQUEST_TEMPLATE/plugin.md index 60ae4b38..8ddf9d34 100644 --- a/.github/PULL_REQUEST_TEMPLATE/plugin.md +++ b/.github/PULL_REQUEST_TEMPLATE/plugin.md @@ -11,6 +11,7 @@ One-liner – What does this plugin contribution add or change? ## Plugin Documentation Checklist - README Validation + - [ ] `README.md` file exists in the plugin root folder - [ ] Clear installation instructions - [ ] Usage examples with code snippets - [ ] List of features and capabilities @@ -18,6 +19,7 @@ One-liner – What does this plugin contribution add or change? - [ ] Contribution guidelines (if applicable) - Metadata Validation + - [ ] `plugin_metadata.yml` file exists in the plugin root folder - [ ] Complete metadata provided in reference to [plugin metadata template](../.././plugins/plugin_metadata_template.yml) ## Dev Testing diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 658b2d29..a292bf4e 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -3,7 +3,7 @@ name: Auto Label PR on: pull_request: branches: [ "main" ] - types: [opened, edited, reopened, synchronize] + types: [ opened, edited, reopened, synchronize ] jobs: label-plugin-pr: diff --git a/.github/workflows/validate-new-plugin-metadata.yml b/.github/workflows/validate-new-plugin-metadata.yml new file mode 100644 index 00000000..6ae10bce --- /dev/null +++ b/.github/workflows/validate-new-plugin-metadata.yml @@ -0,0 +1,164 @@ +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.ref }} + + - 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: | + 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 + + # Function to check if a required field is missing + check_required_field() { + local field="$1" + local value=$(echo "$metadata" | yq ".$field") + if [[ -z "$value" ]]; then + echo "::error file=$metadata_file::'$field' is missing" + ((errors++)) + fi + } + + # Function to validate a URL field (supports single values and arrays) + check_valid_url() { + local field="$1" + local required="${2:-optional}" # Default to "optional" if no second parameter is provided + local value=$(echo "$metadata" | yq ".$field" || true) + + # If field is missing or empty + if [[ -z "$value" ]]; then + if [[ "$required" == "required" ]]; then + echo "::error file=$metadata_file::'$field' is required but missing" + ((errors++)) + fi + return 0 # Skip validation if it's optional and missing + fi + + # Handle arrays of URLs (e.g., screenshots) + if [[ "$value" == "["*"]" ]]; then + for url in $(echo "$value" | yq '.[]'); do + if [[ ! "$url" =~ ^https?:\/\/[a-zA-Z0-9.-]+(\.[a-zA-Z]{2,})+(:[0-9]{1,5})?(\/.*)?$ ]]; then + echo "::error file=$metadata_file::'$field' contains an invalid URL: $url" + ((errors++)) + fi + done + else + if [[ ! "$value" =~ ^https?:\/\/[a-zA-Z0-9.-]+(\.[a-zA-Z]{2,})+(:[0-9]{1,5})?(\/.*)?$ ]]; then + echo "::error file=$metadata_file::'$field' is not a valid URL" + ((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" + check_required_field "support_contact" + + # Validate URLs + 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 "screenshots" + + # Validate date format (YYYY-MM) + release_date=$(echo "$metadata" | yq '.release_date') + if [[ -z "$release_date" ]]; then + echo "::error file=$metadata_file::'$release_date' is required but missing" + ((errors++)) + else + if [[ ! "$release_date" =~ ^202[0-9]{1}-[0-9]{2}$ ]]; 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" ]]; then + echo "::error file=$metadata_file::'$x_account_handle' is required but missing" + ((errors++)) + else + if [[ -n "$x_account_handle" && ! "$x_account_handle" =~ ^@[a-zA-Z0-9_]{1,15}$ ]]; 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" ]]; then + echo "::error file=$metadata_file::'$support_contact' is required but missing" + ((errors++)) + else + if [[ ! "$support_contact" =~ ^(https?:\/\/[a-zA-Z0-9.-]+(\.[a-zA-Z]{2,})+(:[0-9]{1,5})?(\/.*)?|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$ ]]; then + echo "::error file=$metadata_file::'support_contact' must be a valid URL or email address" + ((errors++)) + fi + fi + + echo "::endgroup::" + exit $errors diff --git a/plugins/allora/try_new_file.txt b/plugins/allora/try_new_file.txt new file mode 100644 index 00000000..e69de29b diff --git a/plugins/new_plugin/plugin_metadata.yml b/plugins/new_plugin/plugin_metadata.yml new file mode 100644 index 00000000..3cf99688 --- /dev/null +++ b/plugins/new_plugin/plugin_metadata.yml @@ -0,0 +1,23 @@ +# General Information +plugin_name: "Plugin's Name" +author: "Plugin's Author" +logo_url: "" +release_date: "" + +# Description +short_description: "Plugin's short description" +detailed_description: "Plugin's detailed description" + +# Media & Assets +plugin_logo_url: "" +screenshots: + - "" + - "" +demo_video_url: "" +documentation_url: "" +changelog_url: "" + +# Contact & Support +x_account_handle: "" +support_contact: "Plugin's support contact in wrong format" +community_url: "" diff --git a/plugins/new_plugin/test.txt b/plugins/new_plugin/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/plugins/plugin_metadata_template.yml b/plugins/plugin_metadata_template.yml index cc8fde05..f38d847e 100644 --- a/plugins/plugin_metadata_template.yml +++ b/plugins/plugin_metadata_template.yml @@ -1,8 +1,8 @@ # General Information plugin_name: "" # Name of the plugin author: "" # Author and team name -logo_url: "" # URL to the author photo or team logo (512x512 recommended) -release_date: "" # Release date (DD-MM-YYYY) +logo_url: "" # URL to the author photo or team logo (512x512 recommended) (if any) +release_date: "" # Release date (YYYY-MM) # Description short_description: "" # One-liner description for listings @@ -10,14 +10,14 @@ detailed_description: "" # Full description with features and benefits # Media & Assets plugin_logo_url: "" # URL to the plugin logo (512x512 recommended) (if any or fallback to logo_url) -screenshots: # List of screenshots showcasing the plugin +screenshots: # List of screenshots showcasing the plugin (if any) - "" # e.g., "https://example.com/screenshot1.png" - "" -demo_video_url: "" # Link to a demo or walkthrough video (if available) -documentation_url: "" # Link to the plugin's official documentation (if available) +demo_video_url: "" # Link to a demo or walkthrough video (if any) +documentation_url: "" # Link to the plugin's official documentation (if any) changelog_url: "" # Link to the changelog (if maintained) # Contact & Support x_account_handle: "" # X (formerly known as Twitter) account handle (ie: @GAME_Virtuals) support_contact: "" # Email or Slack/Discord link for user support -community_link: "" # Forum or community link (if any) +community_url: "" # Forum or community link (if any)