Initial commit
This commit is contained in:
331
commands/schema-validation/.scripts/field-checker.sh
Executable file
331
commands/schema-validation/.scripts/field-checker.sh
Executable file
@@ -0,0 +1,331 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ============================================================================
|
||||
# Field Checker Script
|
||||
# ============================================================================
|
||||
# Purpose: Verify required and recommended fields in plugin/marketplace configs
|
||||
# Version: 1.0.0
|
||||
# Usage: ./field-checker.sh <config-file> <type> [strict]
|
||||
# Returns: 0=all required present, 1=missing required, 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 CONFIG_FILE="${1:-}"
|
||||
readonly TYPE="${2:-}"
|
||||
readonly STRICT="${3:-false}"
|
||||
|
||||
# ====================
|
||||
# Field Definitions
|
||||
# ====================
|
||||
|
||||
# Plugin required fields
|
||||
PLUGIN_REQUIRED_FIELDS=(
|
||||
"name"
|
||||
"version"
|
||||
"description"
|
||||
"author"
|
||||
"license"
|
||||
)
|
||||
|
||||
# Plugin recommended fields
|
||||
PLUGIN_RECOMMENDED_FIELDS=(
|
||||
"repository"
|
||||
"homepage"
|
||||
"keywords"
|
||||
"category"
|
||||
)
|
||||
|
||||
# Marketplace required fields
|
||||
MARKETPLACE_REQUIRED_FIELDS=(
|
||||
"name"
|
||||
"owner"
|
||||
"owner.name"
|
||||
"owner.email"
|
||||
"plugins"
|
||||
)
|
||||
|
||||
# Marketplace recommended fields
|
||||
MARKETPLACE_RECOMMENDED_FIELDS=(
|
||||
"version"
|
||||
"metadata.description"
|
||||
"metadata.homepage"
|
||||
"metadata.repository"
|
||||
)
|
||||
|
||||
# ====================
|
||||
# Validation Functions
|
||||
# ====================
|
||||
|
||||
check_field_exists() {
|
||||
local file="$1"
|
||||
local field="$2"
|
||||
local value
|
||||
|
||||
# Use json_get from validate-lib.sh
|
||||
value=$(json_get "${file}" ".${field}")
|
||||
|
||||
if [[ -n "${value}" && "${value}" != "null" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
get_field_value() {
|
||||
local file="$1"
|
||||
local field="$2"
|
||||
|
||||
json_get "${file}" ".${field}"
|
||||
}
|
||||
|
||||
check_field_empty() {
|
||||
local value="$1"
|
||||
|
||||
if [[ -z "${value}" || "${value}" == "null" || "${value}" == '""' || "${value}" == "[]" || "${value}" == "{}" ]]; then
|
||||
return 0 # Is empty
|
||||
else
|
||||
return 1 # Not empty
|
||||
fi
|
||||
}
|
||||
|
||||
# ====================
|
||||
# Plugin Validation
|
||||
# ====================
|
||||
|
||||
validate_plugin_fields() {
|
||||
local file="$1"
|
||||
local strict="$2"
|
||||
local missing_required=0
|
||||
local missing_recommended=0
|
||||
|
||||
print_section "Required Fields (${#PLUGIN_REQUIRED_FIELDS[@]})"
|
||||
|
||||
for field in "${PLUGIN_REQUIRED_FIELDS[@]}"; do
|
||||
if check_field_exists "${file}" "${field}"; then
|
||||
local value
|
||||
value=$(get_field_value "${file}" "${field}")
|
||||
|
||||
# Check if empty
|
||||
if check_field_empty "${value}"; then
|
||||
print_error "${field}: Present but empty (REQUIRED)"
|
||||
((missing_required++))
|
||||
else
|
||||
# Truncate long values for display
|
||||
if [[ "${#value}" -gt 50 ]]; then
|
||||
value="${value:0:47}..."
|
||||
fi
|
||||
print_success "${field}: \"${value}\""
|
||||
fi
|
||||
else
|
||||
print_error "${field}: Missing (REQUIRED)"
|
||||
((missing_required++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
print_section "Recommended Fields (${#PLUGIN_RECOMMENDED_FIELDS[@]})"
|
||||
|
||||
for field in "${PLUGIN_RECOMMENDED_FIELDS[@]}"; do
|
||||
if check_field_exists "${file}" "${field}"; then
|
||||
print_success "${field}: Present"
|
||||
else
|
||||
print_warning "${field}: Missing (improves quality)"
|
||||
((missing_recommended++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
if [[ ${missing_required} -eq 0 ]]; then
|
||||
print_success "All required fields present"
|
||||
else
|
||||
print_error "Missing ${missing_required} required field(s)"
|
||||
fi
|
||||
|
||||
if [[ ${missing_recommended} -gt 0 ]]; then
|
||||
print_info "Missing ${missing_recommended} recommended field(s)"
|
||||
fi
|
||||
|
||||
# Determine exit code
|
||||
if [[ ${missing_required} -gt 0 ]]; then
|
||||
return 1
|
||||
elif [[ "${strict}" == "true" && ${missing_recommended} -gt 0 ]]; then
|
||||
print_warning "Strict mode: Recommended fields are required"
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# ====================
|
||||
# Marketplace Validation
|
||||
# ====================
|
||||
|
||||
validate_marketplace_fields() {
|
||||
local file="$1"
|
||||
local strict="$2"
|
||||
local missing_required=0
|
||||
local missing_recommended=0
|
||||
|
||||
print_section "Required Fields (${#MARKETPLACE_REQUIRED_FIELDS[@]})"
|
||||
|
||||
for field in "${MARKETPLACE_REQUIRED_FIELDS[@]}"; do
|
||||
if check_field_exists "${file}" "${field}"; then
|
||||
local value
|
||||
value=$(get_field_value "${file}" "${field}")
|
||||
|
||||
# Special handling for plugins array
|
||||
if [[ "${field}" == "plugins" ]]; then
|
||||
local count
|
||||
count=$(get_json_array_length "${file}" ".plugins")
|
||||
if [[ ${count} -gt 0 ]]; then
|
||||
print_success "${field}: Array with ${count} entries"
|
||||
else
|
||||
print_error "${field}: Present but empty (REQUIRED)"
|
||||
((missing_required++))
|
||||
fi
|
||||
elif check_field_empty "${value}"; then
|
||||
print_error "${field}: Present but empty (REQUIRED)"
|
||||
((missing_required++))
|
||||
else
|
||||
# Truncate long values
|
||||
if [[ "${#value}" -gt 50 ]]; then
|
||||
value="${value:0:47}..."
|
||||
fi
|
||||
print_success "${field}: \"${value}\""
|
||||
fi
|
||||
else
|
||||
print_error "${field}: Missing (REQUIRED)"
|
||||
((missing_required++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
print_section "Recommended Fields (${#MARKETPLACE_RECOMMENDED_FIELDS[@]})"
|
||||
|
||||
for field in "${MARKETPLACE_RECOMMENDED_FIELDS[@]}"; do
|
||||
if check_field_exists "${file}" "${field}"; then
|
||||
print_success "${field}: Present"
|
||||
else
|
||||
print_warning "${field}: Missing (improves quality)"
|
||||
((missing_recommended++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
if [[ ${missing_required} -eq 0 ]]; then
|
||||
print_success "All required fields present"
|
||||
else
|
||||
print_error "Missing ${missing_required} required field(s)"
|
||||
fi
|
||||
|
||||
if [[ ${missing_recommended} -gt 0 ]]; then
|
||||
print_info "Missing ${missing_recommended} recommended field(s)"
|
||||
fi
|
||||
|
||||
# Determine exit code
|
||||
if [[ ${missing_required} -gt 0 ]]; then
|
||||
return 1
|
||||
elif [[ "${strict}" == "true" && ${missing_recommended} -gt 0 ]]; then
|
||||
print_warning "Strict mode: Recommended fields are required"
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# ====================
|
||||
# Main Logic
|
||||
# ====================
|
||||
|
||||
main() {
|
||||
# Validate arguments
|
||||
if [[ -z "${CONFIG_FILE}" ]]; then
|
||||
print_error "Usage: $0 <config-file> <type> [strict]"
|
||||
print_info "Types: plugin, marketplace"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ ! -f "${CONFIG_FILE}" ]]; then
|
||||
print_error "Configuration file not found: ${CONFIG_FILE}"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "${TYPE}" ]]; then
|
||||
print_error "Type required: plugin or marketplace"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Validate JSON syntax first
|
||||
if ! validate_json_syntax "${CONFIG_FILE}"; then
|
||||
print_error "Invalid JSON syntax in ${CONFIG_FILE}"
|
||||
print_info "Run JSON validation first to fix syntax errors"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Print header
|
||||
print_header "Required Fields Validation"
|
||||
echo "Target: ${CONFIG_FILE}"
|
||||
echo "Type: ${TYPE}"
|
||||
echo "Strict Mode: ${STRICT}"
|
||||
echo ""
|
||||
|
||||
# Validate based on type
|
||||
case "${TYPE}" in
|
||||
plugin)
|
||||
if validate_plugin_fields "${CONFIG_FILE}" "${STRICT}"; then
|
||||
echo ""
|
||||
print_header "✅ PASS: All required fields present"
|
||||
exit 0
|
||||
else
|
||||
echo ""
|
||||
print_header "❌ FAIL: Missing required fields"
|
||||
|
||||
# Show remediation
|
||||
echo ""
|
||||
print_info "Action Required:"
|
||||
echo " Add missing required fields to ${CONFIG_FILE}"
|
||||
echo " Refer to plugin schema: .claude/docs/plugins/plugins-reference.md"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
marketplace)
|
||||
if validate_marketplace_fields "${CONFIG_FILE}" "${STRICT}"; then
|
||||
echo ""
|
||||
print_header "✅ PASS: All required fields present"
|
||||
exit 0
|
||||
else
|
||||
echo ""
|
||||
print_header "❌ FAIL: Missing required fields"
|
||||
|
||||
# Show remediation
|
||||
echo ""
|
||||
print_info "Action Required:"
|
||||
echo " Add missing required fields to ${CONFIG_FILE}"
|
||||
echo " Refer to marketplace schema: .claude/docs/plugins/plugin-marketplaces.md"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown type: ${TYPE}"
|
||||
print_info "Valid types: plugin, marketplace"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
448
commands/schema-validation/.scripts/format-validator.py
Executable file
448
commands/schema-validation/.scripts/format-validator.py
Executable file
@@ -0,0 +1,448 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ============================================================================
|
||||
# Format Validator Script
|
||||
# ============================================================================
|
||||
# Purpose: Validate format compliance for semver, URLs, emails, naming
|
||||
# Version: 1.0.0
|
||||
# Usage: ./format-validator.py --file <path> --type <plugin|marketplace> [--strict]
|
||||
# Returns: 0=all valid, 1=format violations, 2=error
|
||||
# ============================================================================
|
||||
|
||||
import json
|
||||
import sys
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
|
||||
# ====================
|
||||
# Color Support
|
||||
# ====================
|
||||
|
||||
class Colors:
|
||||
"""ANSI color codes for terminal output"""
|
||||
RED = '\033[0;31m'
|
||||
GREEN = '\033[0;32m'
|
||||
YELLOW = '\033[1;33m'
|
||||
BLUE = '\033[0;34m'
|
||||
CYAN = '\033[0;36m'
|
||||
BOLD = '\033[1m'
|
||||
NC = '\033[0m'
|
||||
|
||||
@classmethod
|
||||
def disable(cls):
|
||||
"""Disable colors for non-TTY output"""
|
||||
cls.RED = cls.GREEN = cls.YELLOW = cls.BLUE = cls.CYAN = cls.BOLD = cls.NC = ''
|
||||
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
Colors.disable()
|
||||
|
||||
|
||||
# ====================
|
||||
# Format Patterns
|
||||
# ====================
|
||||
|
||||
# Semantic versioning: X.Y.Z
|
||||
SEMVER_PATTERN = re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$')
|
||||
|
||||
# Lowercase-hyphen naming: plugin-name
|
||||
LOWERCASE_HYPHEN_PATTERN = re.compile(r'^[a-z0-9]+(-[a-z0-9]+)*$')
|
||||
|
||||
# Email: RFC 5322 simplified
|
||||
EMAIL_PATTERN = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
|
||||
|
||||
# URL: http or https
|
||||
URL_PATTERN = re.compile(r'^https?://')
|
||||
|
||||
# HTTPS only
|
||||
HTTPS_PATTERN = re.compile(r'^https://')
|
||||
|
||||
# SPDX License Identifiers (common ones)
|
||||
SPDX_LICENSES = [
|
||||
'MIT', 'Apache-2.0', 'GPL-3.0', 'GPL-2.0', 'LGPL-3.0', 'LGPL-2.1',
|
||||
'BSD-2-Clause', 'BSD-3-Clause', 'ISC', 'MPL-2.0', 'AGPL-3.0',
|
||||
'Unlicense', 'CC0-1.0', 'Proprietary'
|
||||
]
|
||||
|
||||
# Approved categories (10 standard)
|
||||
APPROVED_CATEGORIES = [
|
||||
'development', 'testing', 'deployment', 'documentation', 'security',
|
||||
'database', 'monitoring', 'productivity', 'quality', 'collaboration'
|
||||
]
|
||||
|
||||
|
||||
# ====================
|
||||
# Validation Functions
|
||||
# ====================
|
||||
|
||||
class FormatValidator:
|
||||
"""Format validation logic"""
|
||||
|
||||
def __init__(self, strict_https: bool = False):
|
||||
self.strict_https = strict_https
|
||||
self.errors: List[Tuple[str, str, str]] = []
|
||||
self.warnings: List[Tuple[str, str]] = []
|
||||
self.passed: List[Tuple[str, str]] = []
|
||||
|
||||
def validate_semver(self, field: str, value: str) -> bool:
|
||||
"""Validate semantic versioning"""
|
||||
if not value:
|
||||
return True # Skip empty (handled by required fields check)
|
||||
|
||||
if SEMVER_PATTERN.match(value):
|
||||
self.passed.append((field, f'"{value}" (semver)'))
|
||||
return True
|
||||
else:
|
||||
error = (
|
||||
field,
|
||||
f'"{value}"',
|
||||
'Invalid: Must use semantic versioning (X.Y.Z)\n'
|
||||
' Pattern: MAJOR.MINOR.PATCH\n'
|
||||
' Example: 1.0.0, 2.1.5'
|
||||
)
|
||||
self.errors.append(error)
|
||||
return False
|
||||
|
||||
def validate_lowercase_hyphen(self, field: str, value: str) -> bool:
|
||||
"""Validate lowercase-hyphen naming"""
|
||||
if not value:
|
||||
return True
|
||||
|
||||
if LOWERCASE_HYPHEN_PATTERN.match(value):
|
||||
self.passed.append((field, f'"{value}" (lowercase-hyphen)'))
|
||||
return True
|
||||
else:
|
||||
error = (
|
||||
field,
|
||||
f'"{value}"',
|
||||
'Invalid: Must use lowercase-hyphen format\n'
|
||||
' Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$\n'
|
||||
' Example: my-plugin, test-tool, plugin123'
|
||||
)
|
||||
self.errors.append(error)
|
||||
return False
|
||||
|
||||
def validate_email(self, field: str, value: str) -> bool:
|
||||
"""Validate email address"""
|
||||
if not value:
|
||||
return True
|
||||
|
||||
if EMAIL_PATTERN.match(value):
|
||||
self.passed.append((field, f'"{value}" (valid email)'))
|
||||
return True
|
||||
else:
|
||||
error = (
|
||||
field,
|
||||
f'"{value}"',
|
||||
'Invalid: Must be valid email address\n'
|
||||
' Pattern: user@domain.tld\n'
|
||||
' Example: developer@example.com'
|
||||
)
|
||||
self.errors.append(error)
|
||||
return False
|
||||
|
||||
def validate_url(self, field: str, value: str) -> bool:
|
||||
"""Validate URL format"""
|
||||
if not value:
|
||||
return True
|
||||
|
||||
if self.strict_https and not HTTPS_PATTERN.match(value):
|
||||
error = (
|
||||
field,
|
||||
f'"{value}"',
|
||||
'Invalid: HTTPS required in strict mode\n'
|
||||
f' Current: {value}\n'
|
||||
f' Required: {value.replace("http://", "https://", 1)}'
|
||||
)
|
||||
self.errors.append(error)
|
||||
return False
|
||||
elif URL_PATTERN.match(value):
|
||||
if value.startswith('http://'):
|
||||
self.warnings.append((
|
||||
field,
|
||||
f'"{value}" - Consider using HTTPS for security'
|
||||
))
|
||||
self.passed.append((field, f'"{value}" (valid URL)'))
|
||||
return True
|
||||
else:
|
||||
error = (
|
||||
field,
|
||||
f'"{value}"',
|
||||
'Invalid: Must be valid URL\n'
|
||||
' Pattern: https://domain.tld/path\n'
|
||||
' Example: https://github.com/user/repo'
|
||||
)
|
||||
self.errors.append(error)
|
||||
return False
|
||||
|
||||
def validate_license(self, field: str, value: str) -> bool:
|
||||
"""Validate SPDX license identifier"""
|
||||
if not value:
|
||||
return True
|
||||
|
||||
if value in SPDX_LICENSES:
|
||||
self.passed.append((field, f'"{value}" (SPDX identifier)'))
|
||||
return True
|
||||
else:
|
||||
error = (
|
||||
field,
|
||||
f'"{value}"',
|
||||
'Invalid: Must be SPDX license identifier\n'
|
||||
' Common: MIT, Apache-2.0, GPL-3.0, BSD-3-Clause, ISC\n'
|
||||
' See: https://spdx.org/licenses/'
|
||||
)
|
||||
self.errors.append(error)
|
||||
return False
|
||||
|
||||
def validate_category(self, field: str, value: str) -> bool:
|
||||
"""Validate category against approved list"""
|
||||
if not value:
|
||||
return True
|
||||
|
||||
if value in APPROVED_CATEGORIES:
|
||||
self.passed.append((field, f'"{value}" (approved category)'))
|
||||
return True
|
||||
else:
|
||||
error = (
|
||||
field,
|
||||
f'"{value}"',
|
||||
'Invalid: Must be one of 10 approved categories\n'
|
||||
' Valid: development, testing, deployment, documentation,\n'
|
||||
' security, database, monitoring, productivity,\n'
|
||||
' quality, collaboration'
|
||||
)
|
||||
self.errors.append(error)
|
||||
return False
|
||||
|
||||
def validate_description_length(self, field: str, value: str) -> bool:
|
||||
"""Validate description length (50-200 chars recommended)"""
|
||||
if not value:
|
||||
return True
|
||||
|
||||
length = len(value)
|
||||
if 50 <= length <= 200:
|
||||
self.passed.append((field, f'Valid length ({length} chars)'))
|
||||
return True
|
||||
elif length < 50:
|
||||
self.warnings.append((
|
||||
field,
|
||||
f'Short description ({length} chars) - consider 50-200 characters for clarity'
|
||||
))
|
||||
return True
|
||||
else:
|
||||
self.warnings.append((
|
||||
field,
|
||||
f'Long description ({length} chars) - consider keeping under 200 characters'
|
||||
))
|
||||
return True
|
||||
|
||||
|
||||
# ====================
|
||||
# Plugin Validation
|
||||
# ====================
|
||||
|
||||
def validate_plugin_formats(data: Dict, validator: FormatValidator) -> int:
|
||||
"""Validate plugin format compliance"""
|
||||
print(f"{Colors.CYAN}Format Checks:{Colors.NC}\n")
|
||||
|
||||
# name: lowercase-hyphen
|
||||
if 'name' in data:
|
||||
validator.validate_lowercase_hyphen('name', data['name'])
|
||||
|
||||
# version: semver
|
||||
if 'version' in data:
|
||||
validator.validate_semver('version', data['version'])
|
||||
|
||||
# description: length check
|
||||
if 'description' in data:
|
||||
validator.validate_description_length('description', data['description'])
|
||||
|
||||
# license: SPDX
|
||||
if 'license' in data:
|
||||
validator.validate_license('license', data['license'])
|
||||
|
||||
# homepage: URL
|
||||
if 'homepage' in data:
|
||||
validator.validate_url('homepage', data['homepage'])
|
||||
|
||||
# repository: URL or object
|
||||
if 'repository' in data:
|
||||
repo = data['repository']
|
||||
if isinstance(repo, str):
|
||||
validator.validate_url('repository', repo)
|
||||
elif isinstance(repo, dict) and 'url' in repo:
|
||||
validator.validate_url('repository.url', repo['url'])
|
||||
|
||||
# category: approved list
|
||||
if 'category' in data:
|
||||
validator.validate_category('category', data['category'])
|
||||
|
||||
# author: email if object
|
||||
if 'author' in data:
|
||||
author = data['author']
|
||||
if isinstance(author, dict) and 'email' in author:
|
||||
validator.validate_email('author.email', author['email'])
|
||||
|
||||
return 0 if not validator.errors else 1
|
||||
|
||||
|
||||
# ====================
|
||||
# Marketplace Validation
|
||||
# ====================
|
||||
|
||||
def validate_marketplace_formats(data: Dict, validator: FormatValidator) -> int:
|
||||
"""Validate marketplace format compliance"""
|
||||
print(f"{Colors.CYAN}Format Checks:{Colors.NC}\n")
|
||||
|
||||
# name: lowercase-hyphen
|
||||
if 'name' in data:
|
||||
validator.validate_lowercase_hyphen('name', data['name'])
|
||||
|
||||
# owner.email: email
|
||||
if 'owner' in data and isinstance(data['owner'], dict):
|
||||
if 'email' in data['owner']:
|
||||
validator.validate_email('owner.email', data['owner']['email'])
|
||||
|
||||
# version: semver (if present)
|
||||
if 'version' in data:
|
||||
validator.validate_semver('version', data['version'])
|
||||
|
||||
# metadata fields
|
||||
if 'metadata' in data and isinstance(data['metadata'], dict):
|
||||
metadata = data['metadata']
|
||||
|
||||
if 'description' in metadata:
|
||||
validator.validate_description_length('metadata.description', metadata['description'])
|
||||
|
||||
if 'homepage' in metadata:
|
||||
validator.validate_url('metadata.homepage', metadata['homepage'])
|
||||
|
||||
if 'repository' in metadata:
|
||||
validator.validate_url('metadata.repository', metadata['repository'])
|
||||
|
||||
return 0 if not validator.errors else 1
|
||||
|
||||
|
||||
# ====================
|
||||
# Output Formatting
|
||||
# ====================
|
||||
|
||||
def print_results(validator: FormatValidator):
|
||||
"""Print validation results"""
|
||||
print()
|
||||
|
||||
# Passed checks
|
||||
if validator.passed:
|
||||
for field, msg in validator.passed:
|
||||
print(f" {Colors.GREEN}✅ {field}: {msg}{Colors.NC}")
|
||||
|
||||
# Errors
|
||||
if validator.errors:
|
||||
print()
|
||||
for field, value, msg in validator.errors:
|
||||
print(f" {Colors.RED}❌ {field}: {value}{Colors.NC}")
|
||||
for line in msg.split('\n'):
|
||||
print(f" {line}")
|
||||
print()
|
||||
|
||||
# Warnings
|
||||
if validator.warnings:
|
||||
print()
|
||||
for field, msg in validator.warnings:
|
||||
print(f" {Colors.YELLOW}⚠️ {field}: {msg}{Colors.NC}")
|
||||
|
||||
# Summary
|
||||
print()
|
||||
print(f"{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.NC}")
|
||||
|
||||
total = len(validator.passed) + len(validator.errors)
|
||||
passed_count = len(validator.passed)
|
||||
|
||||
if validator.errors:
|
||||
print(f"{Colors.RED}Failed: {len(validator.errors)}{Colors.NC}")
|
||||
if validator.warnings:
|
||||
print(f"{Colors.YELLOW}Warnings: {len(validator.warnings)}{Colors.NC}")
|
||||
print(f"Status: {Colors.RED}FAIL{Colors.NC}")
|
||||
else:
|
||||
print(f"Passed: {passed_count}/{total}")
|
||||
if validator.warnings:
|
||||
print(f"{Colors.YELLOW}Warnings: {len(validator.warnings)}{Colors.NC}")
|
||||
print(f"Status: {Colors.GREEN}PASS{Colors.NC}")
|
||||
|
||||
|
||||
# ====================
|
||||
# Main Logic
|
||||
# ====================
|
||||
|
||||
def main():
|
||||
"""CLI entry point"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate format compliance for plugin and marketplace configurations',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--file',
|
||||
type=str,
|
||||
required=True,
|
||||
help='Path to configuration file (plugin.json or marketplace.json)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--type',
|
||||
type=str,
|
||||
choices=['plugin', 'marketplace'],
|
||||
required=True,
|
||||
help='Configuration type'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--strict',
|
||||
action='store_true',
|
||||
help='Enforce HTTPS for all URLs'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load configuration file
|
||||
try:
|
||||
with open(args.file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"{Colors.RED}❌ File not found: {args.file}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"{Colors.RED}❌ Invalid JSON: {e}{Colors.NC}", file=sys.stderr)
|
||||
print(f"{Colors.BLUE}ℹ️ Run JSON validation first{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# Print header
|
||||
print(f"{Colors.BOLD}{Colors.BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.NC}")
|
||||
print(f"{Colors.BOLD}Format Validation{Colors.NC}")
|
||||
print(f"{Colors.BOLD}{Colors.BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.NC}")
|
||||
print(f"Target: {args.file}")
|
||||
print(f"Type: {args.type}")
|
||||
if args.strict:
|
||||
print(f"Strict HTTPS: {Colors.GREEN}Enforced{Colors.NC}")
|
||||
print()
|
||||
|
||||
# Create validator
|
||||
validator = FormatValidator(strict_https=args.strict)
|
||||
|
||||
# Validate based on type
|
||||
if args.type == 'plugin':
|
||||
result = validate_plugin_formats(data, validator)
|
||||
else:
|
||||
result = validate_marketplace_formats(data, validator)
|
||||
|
||||
# Print results
|
||||
print_results(validator)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
263
commands/schema-validation/.scripts/json-validator.py
Executable file
263
commands/schema-validation/.scripts/json-validator.py
Executable file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ============================================================================
|
||||
# JSON Validator Script
|
||||
# ============================================================================
|
||||
# Purpose: Multi-backend JSON syntax validation with detailed error reporting
|
||||
# Version: 1.0.0
|
||||
# Usage: ./json-validator.py --file <path> [--verbose]
|
||||
# Returns: 0=valid, 1=invalid, 2=error
|
||||
# Backends: jq (preferred), python3 json module (fallback)
|
||||
# ============================================================================
|
||||
|
||||
import json
|
||||
import sys
|
||||
import argparse
|
||||
import subprocess
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# ====================
|
||||
# Color Support
|
||||
# ====================
|
||||
|
||||
class Colors:
|
||||
"""ANSI color codes for terminal output"""
|
||||
RED = '\033[0;31m'
|
||||
GREEN = '\033[0;32m'
|
||||
YELLOW = '\033[1;33m'
|
||||
BLUE = '\033[0;34m'
|
||||
CYAN = '\033[0;36m'
|
||||
BOLD = '\033[1m'
|
||||
NC = '\033[0m'
|
||||
|
||||
@classmethod
|
||||
def disable(cls):
|
||||
"""Disable colors for non-TTY output"""
|
||||
cls.RED = cls.GREEN = cls.YELLOW = cls.BLUE = cls.CYAN = cls.BOLD = cls.NC = ''
|
||||
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
Colors.disable()
|
||||
|
||||
|
||||
# ====================
|
||||
# Backend Detection
|
||||
# ====================
|
||||
|
||||
def detect_backend():
|
||||
"""Detect available JSON validation backend"""
|
||||
if shutil.which('jq'):
|
||||
return 'jq'
|
||||
elif sys.version_info >= (3, 0):
|
||||
return 'python3'
|
||||
else:
|
||||
return 'none'
|
||||
|
||||
|
||||
def print_backend_info():
|
||||
"""Print detected backend information"""
|
||||
backend = detect_backend()
|
||||
if backend == 'jq':
|
||||
print(f"{Colors.GREEN}✅ Backend: jq (preferred){Colors.NC}")
|
||||
elif backend == 'python3':
|
||||
print(f"{Colors.YELLOW}⚠️ Backend: python3 (fallback){Colors.NC}")
|
||||
else:
|
||||
print(f"{Colors.RED}❌ No JSON validator available{Colors.NC}")
|
||||
print(f"{Colors.BLUE}ℹ️ Install jq for better error messages: apt-get install jq{Colors.NC}")
|
||||
return backend
|
||||
|
||||
|
||||
# ====================
|
||||
# JQ Backend
|
||||
# ====================
|
||||
|
||||
def validate_with_jq(file_path, verbose=False):
|
||||
"""Validate JSON using jq (provides better error messages)"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['jq', 'empty', file_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"{Colors.GREEN}✅ Valid JSON: {file_path}{Colors.NC}")
|
||||
print(f"Backend: jq")
|
||||
return 0
|
||||
else:
|
||||
# Parse jq error message
|
||||
error_msg = result.stderr.strip()
|
||||
print(f"{Colors.RED}❌ Invalid JSON: {file_path}{Colors.NC}")
|
||||
|
||||
if verbose:
|
||||
print(f"{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.NC}")
|
||||
print(f"{Colors.RED}Error Details:{Colors.NC}")
|
||||
print(f" {error_msg}")
|
||||
print()
|
||||
print(f"{Colors.YELLOW}Remediation:{Colors.NC}")
|
||||
print(" - Check for missing commas between object properties")
|
||||
print(" - Verify bracket matching: [ ] { }")
|
||||
print(" - Ensure proper string quoting")
|
||||
print(" - Use a JSON formatter/linter in your editor")
|
||||
else:
|
||||
# Extract line number if available
|
||||
if "parse error" in error_msg.lower():
|
||||
print(f"Error: {error_msg}")
|
||||
|
||||
return 1
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"{Colors.RED}❌ File not found: {file_path}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}❌ Error running jq: {e}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
# ====================
|
||||
# Python3 Backend
|
||||
# ====================
|
||||
|
||||
def validate_with_python(file_path, verbose=False):
|
||||
"""Validate JSON using Python's json module (universal fallback)"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Attempt to parse JSON
|
||||
json.loads(content)
|
||||
|
||||
print(f"{Colors.GREEN}✅ Valid JSON: {file_path}{Colors.NC}")
|
||||
print(f"Backend: python3")
|
||||
return 0
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"{Colors.RED}❌ File not found: {file_path}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"{Colors.RED}❌ Invalid JSON: {file_path}{Colors.NC}")
|
||||
|
||||
if verbose:
|
||||
print(f"{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.NC}")
|
||||
print(f"{Colors.RED}Error Details:{Colors.NC}")
|
||||
print(f" Line: {e.lineno}")
|
||||
print(f" Column: {e.colno}")
|
||||
print(f" Issue: {e.msg}")
|
||||
print()
|
||||
|
||||
# Show problematic section
|
||||
try:
|
||||
lines = content.split('\n')
|
||||
start = max(0, e.lineno - 3)
|
||||
end = min(len(lines), e.lineno + 2)
|
||||
|
||||
print(f"Problematic Section (lines {start+1}-{end}):")
|
||||
for i in range(start, end):
|
||||
line_num = i + 1
|
||||
marker = "→" if line_num == e.lineno else " "
|
||||
print(f" {marker} {line_num:3d} | {lines[i]}")
|
||||
|
||||
print()
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"{Colors.YELLOW}Remediation:{Colors.NC}")
|
||||
print(" - Check for missing commas between array elements or object properties")
|
||||
print(" - Verify bracket matching: [ ] { }")
|
||||
print(" - Ensure all strings are properly quoted")
|
||||
print(" - Use a JSON formatter/linter in your editor")
|
||||
else:
|
||||
print(f"Error: {e.msg} at line {e.lineno}, column {e.colno}")
|
||||
|
||||
return 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}❌ Error reading file: {e}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
# ====================
|
||||
# Main Validation
|
||||
# ====================
|
||||
|
||||
def validate_json(file_path, verbose=False):
|
||||
"""Main validation function with backend selection"""
|
||||
backend = detect_backend()
|
||||
|
||||
if backend == 'none':
|
||||
print(f"{Colors.RED}❌ No JSON validation backend available{Colors.NC}", file=sys.stderr)
|
||||
print(f"{Colors.BLUE}ℹ️ Install jq or ensure python3 is available{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if backend == 'jq':
|
||||
return validate_with_jq(file_path, verbose)
|
||||
else:
|
||||
return validate_with_python(file_path, verbose)
|
||||
|
||||
|
||||
# ====================
|
||||
# CLI Interface
|
||||
# ====================
|
||||
|
||||
def main():
|
||||
"""CLI entry point"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate JSON file syntax with multi-backend support',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
Examples:
|
||||
./json-validator.py --file plugin.json
|
||||
./json-validator.py --file marketplace.json --verbose
|
||||
./json-validator.py --detect
|
||||
|
||||
Backends:
|
||||
- jq (preferred): Fast, excellent error messages
|
||||
- python3 (fallback): Universal availability
|
||||
|
||||
Exit codes:
|
||||
0: Valid JSON
|
||||
1: Invalid JSON
|
||||
2: File error or backend unavailable
|
||||
'''
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--file',
|
||||
type=str,
|
||||
help='Path to JSON file to validate'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
help='Show detailed error information and remediation'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--detect',
|
||||
action='store_true',
|
||||
help='Detect and display available backend'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Handle backend detection
|
||||
if args.detect:
|
||||
print_backend_info()
|
||||
return 0
|
||||
|
||||
# Validate required arguments
|
||||
if not args.file:
|
||||
parser.print_help()
|
||||
return 2
|
||||
|
||||
# Perform validation
|
||||
return validate_json(args.file, args.verbose)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
265
commands/schema-validation/.scripts/schema-differ.sh
Executable file
265
commands/schema-validation/.scripts/schema-differ.sh
Executable file
@@ -0,0 +1,265 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user