Skip to content

Commit dd880ef

Browse files
committed
chore: implement ci check to validate new plugin metadata
1 parent 68099db commit dd880ef

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed
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

0 commit comments

Comments
 (0)