Skip to content

Commit 9766697

Browse files
committed
2 parents c2a779a + e6fab1c commit 9766697

File tree

4 files changed

+197
-7
lines changed

4 files changed

+197
-7
lines changed

.github/PULL_REQUEST_TEMPLATE/plugin.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ One-liner – What does this plugin contribution add or change?
1111
## Plugin Documentation Checklist
1212

1313
- README Validation
14+
- [ ] `README.md` file exists in the plugin root folder
1415
- [ ] Clear installation instructions
1516
- [ ] Usage examples with code snippets
1617
- [ ] List of features and capabilities
1718
- [ ] Troubleshooting guide (if applicable)
1819
- [ ] Contribution guidelines (if applicable)
1920

2021
- Metadata Validation
22+
- [ ] `plugin_metadata.yml` file exists in the plugin root folder
2123
- [ ] Complete metadata provided in reference to [plugin metadata template](../.././plugins/plugin_metadata_template.yml)
2224

2325
## Dev Testing

.github/workflows/pr-labeler.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Auto Label PR
33
on:
44
pull_request:
55
branches: [ "main" ]
6-
types: [opened, edited, reopened, synchronize]
6+
types: [ opened, edited, reopened, synchronize ]
77

88
jobs:
99
label-plugin-pr:
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
name: Validate New Plugin Metadata
2+
3+
on:
4+
pull_request:
5+
branches: [ "main" ]
6+
paths: [ "plugins/**" ]
7+
types: [ opened, edited, reopened, synchronize ]
8+
9+
jobs:
10+
identify-new-plugins:
11+
runs-on: ubuntu-latest
12+
outputs:
13+
plugin_dirs: ${{ steps.find_new_plugins.outputs.plugin_dirs }}
14+
steps:
15+
- name: Checkout Repository
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
ref: ${{ github.event.pull_request.head.ref }}
20+
21+
- name: Identify New Plugin Directories
22+
id: find_new_plugins
23+
run: |
24+
# Fetch latest base branch state
25+
git fetch origin ${{ github.event.pull_request.base.ref }}
26+
BASE_COMMIT=$(git merge-base origin/${{ github.event.pull_request.base.ref }} HEAD)
27+
28+
# Find newly added plugin directories
29+
NEW_PLUGINS=()
30+
for plugin_dir in $(git diff --diff-filter=A --name-only $BASE_COMMIT...HEAD | grep '^plugins/' | cut -d'/' -f1-2 | sort -u); do
31+
# Ensure directory is completely new (does not exist in base branch)
32+
if ! git rev-parse --verify origin/${{ github.event.pull_request.base.ref }}:"$plugin_dir" &>/dev/null; then
33+
NEW_PLUGINS+=("$plugin_dir")
34+
fi
35+
done
36+
37+
# Exit early if no new plugins were found
38+
if [[ ${#NEW_PLUGINS[@]} -eq 0 ]]; then
39+
echo "plugin_dirs=[]" >> $GITHUB_OUTPUT
40+
exit 0
41+
fi
42+
43+
# Convert plugin directory list to JSON format for use in next job
44+
echo "plugin_dirs=$(jq -nc --argjson arr "$(printf '%s\n' "${NEW_PLUGINS[@]}" | jq -R . | jq -s .)" '$arr')" >> $GITHUB_OUTPUT
45+
46+
validate-individual-plugins:
47+
needs: identify-new-plugins
48+
if: ${{ needs.identify-new-plugins.outputs.plugin_dirs != '[]' }}
49+
runs-on: ubuntu-latest
50+
strategy:
51+
matrix:
52+
plugin_dir: ${{ fromJson(needs.identify-new-plugins.outputs.plugin_dirs) }}
53+
steps:
54+
- name: Checkout Repository
55+
uses: actions/checkout@v4
56+
57+
- name: Validate Plugin Metadata
58+
run: |
59+
set +e # Disable exit on error to allow all fields to be validated
60+
metadata_file="${{ matrix.plugin_dir }}/plugin_metadata.yml"
61+
if [[ ! -f "$metadata_file" ]]; then
62+
echo "::error file=$metadata_file::Missing plugin_metadata.yml"
63+
exit 1
64+
fi
65+
66+
echo "::group::Validating $metadata_file"
67+
68+
metadata=$(yq '.' "$metadata_file")
69+
errors=0
70+
71+
# Regex pattern for a valid URL
72+
url_regex='^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:[0-9]{1,5})?(\/.*)?$'
73+
email_regex='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
74+
yyyy_mm_regex='^202[0-9]{1}-[0-9]{2}$'
75+
x_account_handle_regex='^@[a-zA-Z0-9_]{1,15}$'
76+
77+
# Function to print missing field error
78+
missing_field_error() {
79+
local field="$1"
80+
echo "::error file=$metadata_file::'$field' is required but missing"
81+
((errors++))
82+
}
83+
84+
# Function to check if a required field is missing
85+
check_required_field() {
86+
local field="$1"
87+
local value=$(echo "$metadata" | yq -r ".${field}")
88+
if [[ -z "$value" || "$value" == "null" ]]; then
89+
missing_field_error "$field"
90+
fi
91+
}
92+
93+
# 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")
94+
check_valid_url() {
95+
local field="$1"
96+
local required="${2:-optional}" # Default to "optional" if not specified
97+
local value
98+
local field_type
99+
local non_empty_value=0
100+
101+
value=$(echo "$metadata" | yq -r ".${field}" 2>/dev/null || echo "")
102+
field_type=$(echo "$metadata" | yq -r ".${field} | type" 2>/dev/null || echo "")
103+
104+
# If field is missing or empty
105+
if [[ "$value" == "null" || -z "$value" ]]; then
106+
if [[ "$required" == "required" ]]; then
107+
missing_field_error "$field"
108+
fi
109+
return 0 # Skip validation if optional
110+
fi
111+
112+
# Handle arrays of URLs
113+
if [[ "$field_type" == "!!seq" ]]; then
114+
mapfile -t urls < <(echo "$metadata" | yq -r ".${field} | .[]")
115+
116+
for url in "${urls[@]}"; do
117+
[[ -z "$url" ]] && continue
118+
if [[ ! "$url" =~ $url_regex ]]; then
119+
echo "::error file=$metadata_file::'$field' contains an invalid URL: $url"
120+
((errors++))
121+
else
122+
((non_empty_value++))
123+
fi
124+
done
125+
126+
if [[ $non_empty_value -eq 0 && "$required" == "required" ]]; then
127+
echo "::error file=$metadata_file::'$field' is required but missing valid values"
128+
((errors++))
129+
fi
130+
else
131+
# Single value validation
132+
if [[ ! "$value" =~ $url_regex ]]; then
133+
echo "::error file=$metadata_file::'$field' is not a valid URL: $value"
134+
((errors++))
135+
fi
136+
fi
137+
}
138+
139+
# Validate required fields
140+
check_required_field "plugin_name"
141+
check_required_field "author"
142+
check_required_field "short_description"
143+
check_required_field "detailed_description"
144+
145+
# 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")
146+
check_valid_url "logo_url"
147+
check_valid_url "plugin_logo_url"
148+
check_valid_url "demo_video_url"
149+
check_valid_url "documentation_url"
150+
check_valid_url "changelog_url"
151+
check_valid_url "community_url"
152+
check_valid_url "screenshots"
153+
154+
# Validate date format (YYYY-MM)
155+
release_date=$(echo "$metadata" | yq -r '.release_date')
156+
if [[ -z "$release_date" || "$release_date" == "null" ]]; then
157+
missing_field_error "release_date"
158+
else
159+
if [[ ! "$release_date" =~ $yyyy_mm_regex ]]; then
160+
echo "::error file=$metadata_file::'release_date' should be in YYYY-MM format"
161+
((errors++))
162+
fi
163+
fi
164+
165+
# Validate X account handle format (@username)
166+
x_account_handle=$(echo "$metadata" | yq '.x_account_handle')
167+
if [[ -z "$x_account_handle" || "$x_account_handle" == "null" ]]; then
168+
missing_field_error "x_account_handle"
169+
else
170+
if [[ -n "$x_account_handle" && ! "$x_account_handle" =~ $x_account_handle_regex ]]; then
171+
echo "::error file=$metadata_file::'x_account_handle' is not a valid X (Twitter) handle"
172+
((errors++))
173+
fi
174+
fi
175+
176+
# Validate support contact (must be a valid URL or email)
177+
support_contact=$(echo "$metadata" | yq '.support_contact')
178+
if [[ -z "$support_contact" || "$support_contact" == "null" ]]; then
179+
missing_field_error "support_contact"
180+
else
181+
if [[ ! "$support_contact" =~ ($url_regex|$email_regex) ]]; then
182+
echo "::error file=$metadata_file::'support_contact' must be a valid URL or email address"
183+
((errors++))
184+
fi
185+
fi
186+
187+
echo "::endgroup::"
188+
exit $errors
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
# General Information
22
plugin_name: "" # Name of the plugin
33
author: "" # Author and team name
4-
logo_url: "" # URL to the author photo or team logo (512x512 recommended)
5-
release_date: "" # Release date (DD-MM-YYYY)
4+
logo_url: "" # URL to the author photo or team logo (512x512 recommended) (if any)
5+
release_date: "" # Release date (YYYY-MM)
66

77
# Description
88
short_description: "" # One-liner description for listings
99
detailed_description: "" # Full description with features and benefits
1010

1111
# Media & Assets
1212
plugin_logo_url: "" # URL to the plugin logo (512x512 recommended) (if any or fallback to logo_url)
13-
screenshots: # List of screenshots showcasing the plugin
13+
screenshots: # List of screenshots showcasing the plugin (if any)
1414
- "" # e.g., "https://example.com/screenshot1.png"
1515
- ""
16-
demo_video_url: "" # Link to a demo or walkthrough video (if available)
17-
documentation_url: "" # Link to the plugin's official documentation (if available)
16+
demo_video_url: "" # Link to a demo or walkthrough video (if any)
17+
documentation_url: "" # Link to the plugin's official documentation (if any)
1818
changelog_url: "" # Link to the changelog (if maintained)
1919

2020
# Contact & Support
2121
x_account_handle: "" # X (formerly known as Twitter) account handle (ie: @GAME_Virtuals)
2222
support_contact: "" # Email or Slack/Discord link for user support
23-
community_link: "" # Forum or community link (if any)
23+
community_url: "" # Forum or community link (if any)

0 commit comments

Comments
 (0)