Files
2025-11-29 18:20:28 +08:00

266 lines
8.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# ============================================================================
# Schema Differ Script
# ============================================================================
# Purpose: Compare configuration against reference schemas and validate plugin entries
# Version: 1.0.0
# Usage: ./schema-differ.sh <marketplace-file> [index]
# Returns: 0=all valid, 1=validation errors, 2=error
# ============================================================================
set -euo pipefail
# Source shared library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/../../../scripts/validate-lib.sh"
# ====================
# Configuration
# ====================
readonly MARKETPLACE_FILE="${1:-}"
readonly INDEX="${2:-all}"
# ====================
# Plugin Entry Validation
# ====================
validate_plugin_entry() {
local index=$1
local entry_json=$2
local strict=${3:-false}
local has_errors=0
local has_warnings=0
# Extract fields using json_get would be complex here, so use jq/python inline
local name version source description author keywords license
name=$(echo "${entry_json}" | jq -r '.name // empty' 2>/dev/null || echo "")
version=$(echo "${entry_json}" | jq -r '.version // empty' 2>/dev/null || echo "")
source=$(echo "${entry_json}" | jq -r '.source // empty' 2>/dev/null || echo "")
description=$(echo "${entry_json}" | jq -r '.description // empty' 2>/dev/null || echo "")
author=$(echo "${entry_json}" | jq -r '.author // empty' 2>/dev/null || echo "")
keywords=$(echo "${entry_json}" | jq -r '.keywords // empty' 2>/dev/null || echo "")
license=$(echo "${entry_json}" | jq -r '.license // empty' 2>/dev/null || echo "")
echo ""
print_section "Entry ${index}: ${name:-<unnamed>}"
# Required fields
echo " Required (3):"
# name (required, lowercase-hyphen)
if [[ -z "${name}" ]]; then
print_error " name: Missing (REQUIRED)"
((has_errors++))
elif ! validate_name_format "${name}"; then
print_error " name: \"${name}\" - Invalid format"
print_info " Expected: lowercase-hyphen (my-plugin)"
((has_errors++))
else
print_success " name: \"${name}\""
fi
# source (required, valid format)
if [[ -z "${source}" ]]; then
print_error " source: Missing (REQUIRED)"
((has_errors++))
elif ! validate_source_format "${source}"; then
print_error " source: \"${source}\" - Invalid format"
print_info " Valid: ./path, github:user/repo, https://url"
((has_errors++))
else
print_success " source: \"${source}\""
fi
# description (required, non-empty)
if [[ -z "${description}" ]]; then
print_error " description: Missing (REQUIRED)"
((has_errors++))
else
# Truncate for display
local desc_display="${description}"
if [[ ${#description} -gt 50 ]]; then
desc_display="${description:0:47}..."
fi
print_success " description: \"${desc_display}\""
fi
echo ""
echo " Recommended (4):"
# version (recommended, semver)
if [[ -z "${version}" ]]; then
print_warning " version: Missing"
((has_warnings++))
elif ! validate_semver "${version}"; then
print_warning " version: \"${version}\" - Invalid semver"
((has_warnings++))
else
print_success " version: \"${version}\""
fi
# author (recommended)
if [[ -z "${author}" || "${author}" == "null" ]]; then
print_warning " author: Missing"
((has_warnings++))
else
print_success " author: Present"
fi
# keywords (recommended)
if [[ -z "${keywords}" || "${keywords}" == "null" || "${keywords}" == "[]" ]]; then
print_warning " keywords: Missing"
((has_warnings++))
else
local keyword_count
keyword_count=$(echo "${entry_json}" | jq '.keywords | length' 2>/dev/null || echo "0")
print_success " keywords: ${keyword_count} items"
fi
# license (recommended, SPDX)
if [[ -z "${license}" ]]; then
print_warning " license: Missing"
((has_warnings++))
elif ! validate_license "${license}"; then
print_warning " license: \"${license}\" - Unknown SPDX identifier"
((has_warnings++))
else
print_success " license: \"${license}\""
fi
# Entry status
echo ""
if [[ ${has_errors} -eq 0 ]]; then
if [[ ${has_warnings} -eq 0 ]]; then
print_success "Status: PASS (no issues)"
else
print_info "Status: PASS with ${has_warnings} warning(s)"
fi
return 0
else
print_error "Status: FAIL (${has_errors} critical issues, ${has_warnings} warnings)"
return 1
fi
}
# ====================
# Main Logic
# ====================
main() {
# Validate arguments
if [[ -z "${MARKETPLACE_FILE}" ]]; then
print_error "Usage: $0 <marketplace-file> [index]"
exit 2
fi
if [[ ! -f "${MARKETPLACE_FILE}" ]]; then
print_error "Marketplace file not found: ${MARKETPLACE_FILE}"
exit 2
fi
# Validate JSON syntax
if ! validate_json_syntax "${MARKETPLACE_FILE}"; then
print_error "Invalid JSON in ${MARKETPLACE_FILE}"
print_info "Run JSON validation first to fix syntax errors"
exit 2
fi
# Print header
print_header "Plugin Entries Validation"
echo "Marketplace: ${MARKETPLACE_FILE}"
echo ""
# Get plugins array length
local plugin_count
plugin_count=$(get_json_array_length "${MARKETPLACE_FILE}" ".plugins")
if [[ ${plugin_count} -eq 0 ]]; then
print_warning "No plugin entries found in marketplace"
print_info "The plugins array is empty"
exit 0
fi
echo "Total Entries: ${plugin_count}"
# Determine which entries to validate
local entries_to_check=()
if [[ "${INDEX}" == "all" ]]; then
for ((i=0; i<plugin_count; i++)); do
entries_to_check+=("$i")
done
elif [[ "${INDEX}" =~ ^[0-9]+$ ]]; then
if [[ ${INDEX} -ge ${plugin_count} ]]; then
print_error "Invalid index: ${INDEX} (valid range: 0-$((plugin_count-1)))"
exit 2
fi
entries_to_check=("${INDEX}")
else
print_error "Invalid index: ${INDEX} (must be number or 'all')"
exit 2
fi
# Validate each entry
local failed_count=0
local total_errors=0
local total_warnings=0
for idx in "${entries_to_check[@]}"; do
# Extract plugin entry
local entry_json
if command -v jq &> /dev/null; then
entry_json=$(jq ".plugins[${idx}]" "${MARKETPLACE_FILE}" 2>/dev/null)
else
# Fallback to python
entry_json=$(python3 <<EOF 2>/dev/null
import json
with open('${MARKETPLACE_FILE}') as f:
data = json.load(f)
print(json.dumps(data['plugins'][${idx}]))
EOF
)
fi
# Validate entry
if ! validate_plugin_entry "${idx}" "${entry_json}" "false"; then
((failed_count++))
fi
done
# Summary
echo ""
print_header "Summary"
local passed_count=$((${#entries_to_check[@]} - failed_count))
local pass_percentage=$((passed_count * 100 / ${#entries_to_check[@]}))
echo "Total Entries: ${#entries_to_check[@]}"
if [[ ${passed_count} -gt 0 ]]; then
print_success "Passed: ${passed_count} (${pass_percentage}%)"
fi
if [[ ${failed_count} -gt 0 ]]; then
print_error "Failed: ${failed_count} ($((100 - pass_percentage))%)"
fi
echo ""
if [[ ${failed_count} -eq 0 ]]; then
print_header "✅ PASS: All plugin entries valid"
exit 0
else
print_header "❌ FAIL: ${failed_count} plugin entries have errors"
echo ""
print_info "Action Required:"
echo " Fix validation errors in plugin entries"
echo " Ensure all required fields are present"
echo " Use correct formats for names, versions, and sources"
exit 1
fi
}
main "$@"