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 "$@"
|
||||
241
commands/schema-validation/check-plugin-entries.md
Normal file
241
commands/schema-validation/check-plugin-entries.md
Normal file
@@ -0,0 +1,241 @@
|
||||
## Operation: Check Plugin Entries
|
||||
|
||||
Validate plugin entries in marketplace configuration for completeness and format compliance.
|
||||
|
||||
### Parameters from $ARGUMENTS
|
||||
|
||||
- **marketplace**: Path to marketplace.json file (required)
|
||||
- **strict**: Require all recommended fields in plugin entries (optional, default: false)
|
||||
- **index**: Validate specific plugin entry by index (optional, validates all if not specified)
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Load Marketplace Configuration**
|
||||
```
|
||||
Locate marketplace.json:
|
||||
- Direct path: <marketplace>
|
||||
- Relative: <marketplace>/marketplace.json
|
||||
- Claude plugin: <marketplace>/.claude-plugin/marketplace.json
|
||||
|
||||
Validate JSON syntax first
|
||||
```
|
||||
|
||||
2. **Extract Plugin Entries**
|
||||
```
|
||||
Parse plugins array from marketplace.json
|
||||
Count total entries
|
||||
Determine which entries to validate (all or specific index)
|
||||
```
|
||||
|
||||
3. **Validate Each Plugin Entry**
|
||||
```
|
||||
For each plugin entry, execute .scripts/schema-differ.sh
|
||||
|
||||
Check required fields:
|
||||
- name (string, lowercase-hyphen)
|
||||
- source (string, valid format: ./path, github:, https://)
|
||||
- description (string, non-empty)
|
||||
|
||||
Check recommended fields:
|
||||
- version (string, semver)
|
||||
- author (string or object)
|
||||
- keywords (array, 3-7 items)
|
||||
- license (string, SPDX identifier)
|
||||
|
||||
Validate field formats:
|
||||
- name: lowercase-hyphen pattern
|
||||
- version: semantic versioning
|
||||
- source: valid source format
|
||||
- license: SPDX identifier
|
||||
```
|
||||
|
||||
4. **Aggregate Results**
|
||||
```
|
||||
Per-entry summary:
|
||||
- Entry index
|
||||
- Plugin name
|
||||
- Status: PASS/FAIL
|
||||
- Missing required fields
|
||||
- Missing recommended fields
|
||||
- Format violations
|
||||
|
||||
Overall summary:
|
||||
- Total entries
|
||||
- Passed count
|
||||
- Failed count
|
||||
- Total issues
|
||||
```
|
||||
|
||||
### Plugin Entry Required Fields
|
||||
|
||||
- `name`: Unique plugin identifier (lowercase-hyphen)
|
||||
- `source`: Where to locate plugin (./path, github:user/repo, https://url)
|
||||
- `description`: Brief plugin description (non-empty)
|
||||
|
||||
### Plugin Entry Recommended Fields
|
||||
|
||||
- `version`: Plugin version (semver)
|
||||
- `author`: Plugin author (string or object)
|
||||
- `keywords`: Search keywords (array of 3-7 strings)
|
||||
- `license`: License identifier (SPDX)
|
||||
- `homepage`: Documentation URL
|
||||
- `repository`: Source code URL
|
||||
|
||||
### Source Format Validation
|
||||
|
||||
**Relative Path**:
|
||||
- Pattern: `./` or `../`
|
||||
- Example: `./plugins/my-plugin`
|
||||
|
||||
**GitHub Format**:
|
||||
- Pattern: `github:owner/repo`
|
||||
- Example: `github:anthropics/claude-plugin`
|
||||
|
||||
**Git URL**:
|
||||
- Pattern: `https://...git`
|
||||
- Example: `https://github.com/user/plugin.git`
|
||||
|
||||
**Archive URL**:
|
||||
- Pattern: `https://....(zip|tar.gz|tgz)`
|
||||
- Example: `https://example.com/plugin.zip`
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Validate all plugin entries in marketplace
|
||||
/schema-validation entries marketplace:./test-marketplace
|
||||
|
||||
# Validate with strict mode (require recommended fields)
|
||||
/schema-validation entries marketplace:marketplace.json strict:true
|
||||
|
||||
# Validate specific plugin entry by index
|
||||
/schema-validation entries marketplace:marketplace.json index:0
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **Marketplace not found**: Show searched paths
|
||||
- **Invalid JSON**: Suggest running json validation
|
||||
- **No plugins array**: Error - required field
|
||||
- **Empty plugins array**: Warning - marketplace has no plugins
|
||||
- **Invalid index**: Error with valid range
|
||||
|
||||
### Output Format
|
||||
|
||||
**Success (all entries valid)**:
|
||||
```
|
||||
✅ Plugin Entries Validation: PASS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Marketplace: ./test-marketplace/marketplace.json
|
||||
Total Entries: 3
|
||||
|
||||
Entry 0: code-review ✅
|
||||
Required (3/3):
|
||||
✅ name: "code-review"
|
||||
✅ source: "./plugins/code-review"
|
||||
✅ description: "Automated code review..."
|
||||
|
||||
Recommended (4/4):
|
||||
✅ version: "2.0.0"
|
||||
✅ author: Present
|
||||
✅ keywords: 3 items
|
||||
✅ license: "MIT"
|
||||
|
||||
Entry 1: deploy-tools ✅
|
||||
Required (3/3):
|
||||
✅ name: "deploy-tools"
|
||||
✅ source: "github:company/deploy"
|
||||
✅ description: "Deployment automation..."
|
||||
|
||||
Recommended (3/4):
|
||||
✅ version: "1.5.0"
|
||||
✅ author: Present
|
||||
⚠️ keywords: Missing
|
||||
|
||||
Entry 2: security-scan ✅
|
||||
Required (3/3):
|
||||
✅ name: "security-scan"
|
||||
✅ source: "https://example.com/plugin.zip"
|
||||
✅ description: "Security vulnerability scanning..."
|
||||
|
||||
Summary:
|
||||
Total: 3 entries
|
||||
Passed: 3 (100%)
|
||||
Failed: 0
|
||||
Warnings: 1 (non-blocking)
|
||||
|
||||
Status: PASS
|
||||
```
|
||||
|
||||
**Failure (validation errors)**:
|
||||
```
|
||||
❌ Plugin Entries Validation: FAIL
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Marketplace: marketplace.json
|
||||
Total Entries: 2
|
||||
|
||||
Entry 0: my-plugin ❌
|
||||
Required (2/3):
|
||||
❌ name: "My-Plugin"
|
||||
Invalid: Must use lowercase-hyphen format
|
||||
Expected: my-plugin
|
||||
|
||||
❌ source: Missing (REQUIRED)
|
||||
|
||||
✅ description: "My awesome plugin"
|
||||
|
||||
Recommended (1/4):
|
||||
✅ version: "1.0.0"
|
||||
❌ author: Missing
|
||||
❌ keywords: Missing
|
||||
❌ license: Missing
|
||||
|
||||
Issues: 5 (2 critical, 3 warnings)
|
||||
|
||||
Entry 1: test-tool ✅
|
||||
Required (3/3):
|
||||
✅ name: "test-tool"
|
||||
✅ source: "./plugins/test-tool"
|
||||
✅ description: "Testing utilities"
|
||||
|
||||
Recommended (2/4):
|
||||
⚠️ version: Missing
|
||||
⚠️ author: Missing
|
||||
|
||||
Summary:
|
||||
Total: 2 entries
|
||||
Passed: 1 (50%)
|
||||
Failed: 1 (50%)
|
||||
Critical Issues: 2
|
||||
Warnings: 5
|
||||
|
||||
Status: FAIL
|
||||
|
||||
Action Required:
|
||||
Fix plugin entry #0 (my-plugin):
|
||||
- Change name to lowercase-hyphen: "my-plugin"
|
||||
- Add source field: "./plugins/my-plugin"
|
||||
- Consider adding: author, keywords, license
|
||||
```
|
||||
|
||||
**Empty Marketplace**:
|
||||
```
|
||||
⚠️ Plugin Entries Validation: WARNING
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Marketplace: marketplace.json
|
||||
|
||||
⚠️ No plugin entries found
|
||||
- plugins array is empty
|
||||
- Add at least one plugin entry to marketplace
|
||||
|
||||
Status: WARNING (empty marketplace)
|
||||
```
|
||||
|
||||
### Integration
|
||||
|
||||
This operation is called by:
|
||||
- `full-schema-validation.md` - When validating marketplace type
|
||||
- `validation-orchestrator` - Marketplace comprehensive validation
|
||||
- Direct user invocation for plugin entry checking
|
||||
|
||||
**Request**: $ARGUMENTS
|
||||
183
commands/schema-validation/check-required-fields.md
Normal file
183
commands/schema-validation/check-required-fields.md
Normal file
@@ -0,0 +1,183 @@
|
||||
## Operation: Check Required Fields
|
||||
|
||||
Verify all required fields are present and non-empty in plugin or marketplace configuration.
|
||||
|
||||
### Parameters from $ARGUMENTS
|
||||
|
||||
- **path**: Path to plugin directory or marketplace file (required)
|
||||
- **type**: Target type: `plugin` or `marketplace` (required)
|
||||
- **strict**: Fail on missing recommended fields (optional, default: false)
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Detect Target Type**
|
||||
```
|
||||
IF type not specified:
|
||||
Auto-detect based on path structure:
|
||||
- Has plugin.json → plugin
|
||||
- Has marketplace.json or .claude-plugin/marketplace.json → marketplace
|
||||
- Otherwise → error
|
||||
```
|
||||
|
||||
2. **Locate Configuration File**
|
||||
```
|
||||
For plugin:
|
||||
Check: <path>/plugin.json
|
||||
|
||||
For marketplace:
|
||||
Check: <path>/marketplace.json
|
||||
OR: <path>/.claude-plugin/marketplace.json
|
||||
|
||||
IF not found:
|
||||
Return error with searched paths
|
||||
```
|
||||
|
||||
3. **Execute Field Validation**
|
||||
```
|
||||
Execute .scripts/field-checker.sh "$config_file" "$type" "$strict"
|
||||
|
||||
Returns:
|
||||
- List of required fields: present ✅ or missing ❌
|
||||
- List of recommended fields: present ✅ or missing ⚠️
|
||||
- Overall status: PASS or FAIL
|
||||
```
|
||||
|
||||
4. **Aggregate Results**
|
||||
```
|
||||
Count:
|
||||
- Required missing: critical errors
|
||||
- Recommended missing: warnings
|
||||
|
||||
IF any required missing:
|
||||
Exit with status 1
|
||||
|
||||
IF strict mode AND any recommended missing:
|
||||
Exit with status 1
|
||||
|
||||
Otherwise:
|
||||
Exit with status 0
|
||||
```
|
||||
|
||||
### Required Fields by Type
|
||||
|
||||
**Plugin** (from plugin.json):
|
||||
- `name` (string, lowercase-hyphen format)
|
||||
- `version` (string, semver X.Y.Z)
|
||||
- `description` (string, 50-200 characters)
|
||||
- `author` (string or object with name field)
|
||||
- `license` (string, SPDX identifier)
|
||||
|
||||
**Marketplace** (from marketplace.json):
|
||||
- `name` (string, lowercase-hyphen format)
|
||||
- `owner` (object with name field)
|
||||
- `owner.name` (string)
|
||||
- `owner.email` (string, valid email format)
|
||||
- `plugins` (array, at least one entry)
|
||||
|
||||
### Recommended Fields
|
||||
|
||||
**Plugin**:
|
||||
- `repository` (object or string, source code location)
|
||||
- `homepage` (string, documentation URL)
|
||||
- `keywords` (array, 3-7 relevant keywords)
|
||||
- `category` (string, one of 10 approved categories)
|
||||
|
||||
**Marketplace**:
|
||||
- `version` (string, marketplace version)
|
||||
- `metadata.description` (string, marketplace purpose)
|
||||
- `metadata.homepage` (string, marketplace documentation)
|
||||
- `metadata.repository` (string, marketplace source)
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Check plugin required fields
|
||||
/schema-validation fields path:. type:plugin
|
||||
|
||||
# Check marketplace with strict mode (fail on missing recommended)
|
||||
/schema-validation fields path:./test-marketplace type:marketplace strict:true
|
||||
|
||||
# Auto-detect type
|
||||
/schema-validation fields path:.
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **File not found**: List all searched paths
|
||||
- **Invalid JSON**: Suggest running json validation first
|
||||
- **Unknown type**: Show valid types (plugin, marketplace)
|
||||
- **Empty field**: Report which field is present but empty
|
||||
|
||||
### Output Format
|
||||
|
||||
**Success (all required present)**:
|
||||
```
|
||||
✅ Required Fields Validation: PASS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Target: plugin.json
|
||||
Type: plugin
|
||||
|
||||
Required Fields (5/5):
|
||||
✅ name: "my-plugin"
|
||||
✅ version: "1.0.0"
|
||||
✅ description: "My awesome plugin"
|
||||
✅ author: "Developer Name"
|
||||
✅ license: "MIT"
|
||||
|
||||
Recommended Fields (3/4):
|
||||
✅ repository: Present
|
||||
✅ homepage: Present
|
||||
✅ keywords: Present
|
||||
⚠️ category: Missing (improves discoverability)
|
||||
|
||||
Status: PASS
|
||||
Warnings: 1 (non-blocking)
|
||||
```
|
||||
|
||||
**Failure (missing required)**:
|
||||
```
|
||||
❌ Required Fields Validation: FAIL
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Target: plugin.json
|
||||
Type: plugin
|
||||
|
||||
Required Fields (3/5):
|
||||
✅ name: "my-plugin"
|
||||
❌ version: Missing (REQUIRED - use semver X.Y.Z)
|
||||
✅ description: "My plugin"
|
||||
❌ license: Missing (REQUIRED - use MIT, Apache-2.0, etc.)
|
||||
✅ author: "Developer"
|
||||
|
||||
Critical Issues: 2
|
||||
Status: FAIL
|
||||
|
||||
Action Required:
|
||||
Add missing required fields to plugin.json:
|
||||
- version: "1.0.0"
|
||||
- license: "MIT"
|
||||
```
|
||||
|
||||
**Marketplace Example**:
|
||||
```
|
||||
✅ Required Fields Validation: PASS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Target: marketplace.json
|
||||
Type: marketplace
|
||||
|
||||
Required Fields (5/5):
|
||||
✅ name: "my-marketplace"
|
||||
✅ owner.name: "DevTools Team"
|
||||
✅ owner.email: "devtools@example.com"
|
||||
✅ plugins: Array with 3 entries
|
||||
|
||||
Status: PASS
|
||||
```
|
||||
|
||||
### Integration
|
||||
|
||||
This operation is called by:
|
||||
- `full-schema-validation.md` - Second validation step after JSON syntax
|
||||
- `validation-orchestrator` - Comprehensive validation checks
|
||||
- Direct user invocation for field checking
|
||||
|
||||
**Request**: $ARGUMENTS
|
||||
313
commands/schema-validation/full-schema-validation.md
Normal file
313
commands/schema-validation/full-schema-validation.md
Normal file
@@ -0,0 +1,313 @@
|
||||
## Operation: Full Schema Validation
|
||||
|
||||
Execute complete schema validation workflow: JSON syntax → Required fields → Format compliance → Plugin entries (if marketplace).
|
||||
|
||||
### Parameters from $ARGUMENTS
|
||||
|
||||
- **path**: Path to plugin directory or marketplace (required)
|
||||
- **type**: Target type: `plugin` or `marketplace` (optional, auto-detect)
|
||||
- **strict**: Fail on warnings and missing recommended fields (optional, default: false)
|
||||
- **verbose**: Show detailed error information (optional, default: false)
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Detect Target Type**
|
||||
```
|
||||
IF type not specified:
|
||||
Auto-detect based on path structure:
|
||||
- Has plugin.json at root → plugin
|
||||
- Has marketplace.json → marketplace
|
||||
- Otherwise → error
|
||||
|
||||
Locate configuration file:
|
||||
Plugin: <path>/plugin.json
|
||||
Marketplace: <path>/marketplace.json or <path>/.claude-plugin/marketplace.json
|
||||
```
|
||||
|
||||
2. **Phase 1: JSON Syntax Validation**
|
||||
```
|
||||
Read validate-json.md instructions
|
||||
|
||||
Execute: .scripts/json-validator.py --file "$config" --verbose "$verbose"
|
||||
|
||||
On failure:
|
||||
- Report JSON syntax errors
|
||||
- Stop validation (cannot proceed with invalid JSON)
|
||||
- Exit with status 1
|
||||
|
||||
On success:
|
||||
- Continue to Phase 2
|
||||
```
|
||||
|
||||
3. **Phase 2: Required Fields Check**
|
||||
```
|
||||
Read check-required-fields.md instructions
|
||||
|
||||
Execute: .scripts/field-checker.sh "$config" "$type" "$strict"
|
||||
|
||||
Collect results:
|
||||
- Required fields: present/missing
|
||||
- Recommended fields: present/missing
|
||||
- Critical errors count
|
||||
- Warnings count
|
||||
|
||||
On failure:
|
||||
- Report missing required fields
|
||||
- Continue to Phase 3 (show all issues)
|
||||
|
||||
On success:
|
||||
- Continue to Phase 3
|
||||
```
|
||||
|
||||
4. **Phase 3: Format Validation**
|
||||
```
|
||||
Read validate-formats.md instructions
|
||||
|
||||
Execute: .scripts/format-validator.py --file "$config" --type "$type" --strict "$strict"
|
||||
|
||||
Validate:
|
||||
- Semantic versioning
|
||||
- Lowercase-hyphen naming
|
||||
- URL formats
|
||||
- Email addresses
|
||||
- License identifiers
|
||||
- Category names (if present)
|
||||
|
||||
Collect results:
|
||||
- Format violations count
|
||||
- Warnings count
|
||||
```
|
||||
|
||||
5. **Phase 4: Plugin Entries Validation (Marketplace Only)**
|
||||
```
|
||||
IF type == "marketplace":
|
||||
Read check-plugin-entries.md instructions
|
||||
|
||||
Execute: .scripts/schema-differ.sh "$config" "all"
|
||||
|
||||
Validate each plugin entry:
|
||||
- Required fields (name, source, description)
|
||||
- Recommended fields (version, author, license, keywords)
|
||||
- Format compliance
|
||||
|
||||
Collect results:
|
||||
- Total plugin entries
|
||||
- Passed entries
|
||||
- Failed entries
|
||||
- Total issues per entry
|
||||
```
|
||||
|
||||
6. **Aggregate Results**
|
||||
```
|
||||
Compile all validation phases:
|
||||
Phase 1: JSON Syntax [PASS/FAIL]
|
||||
Phase 2: Required Fields [PASS/FAIL]
|
||||
Phase 3: Format Compliance [PASS/FAIL]
|
||||
Phase 4: Plugin Entries [PASS/FAIL] (marketplace only)
|
||||
|
||||
Calculate overall status:
|
||||
IF any phase FAIL: Overall FAIL
|
||||
IF strict mode AND any warnings: Overall FAIL
|
||||
ELSE: Overall PASS
|
||||
|
||||
Generate summary report:
|
||||
- Total checks performed
|
||||
- Critical errors
|
||||
- Warnings
|
||||
- Overall status
|
||||
- Publication readiness
|
||||
```
|
||||
|
||||
### Exit Codes
|
||||
|
||||
- **0**: All validation passed (or warnings only in non-strict mode)
|
||||
- **1**: Validation failed (critical errors or strict mode with warnings)
|
||||
- **2**: Error (file not found, invalid arguments, etc.)
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Full validation with auto-detect
|
||||
/schema-validation full-schema path:.
|
||||
|
||||
# Full plugin validation with strict mode
|
||||
/schema-validation full-schema path:. type:plugin strict:true
|
||||
|
||||
# Full marketplace validation with verbose output
|
||||
/schema-validation full-schema path:./test-marketplace type:marketplace verbose:true
|
||||
|
||||
# Validate specific plugin in subdirectory
|
||||
/schema-validation full-schema path:./plugins/my-plugin type:plugin
|
||||
```
|
||||
|
||||
### Integration
|
||||
|
||||
This operation is the primary entry point for complete schema validation and is called by:
|
||||
- `validation-orchestrator` comprehensive validation
|
||||
- Marketplace submission workflows
|
||||
- CI/CD validation pipelines
|
||||
- Direct user invocation for thorough checking
|
||||
|
||||
### Output Format
|
||||
|
||||
**Success (all phases pass)**:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
FULL SCHEMA VALIDATION
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Target: plugin.json
|
||||
Type: plugin
|
||||
Mode: Standard
|
||||
|
||||
Phase 1: JSON Syntax ✅
|
||||
Status: Valid JSON
|
||||
Backend: jq
|
||||
|
||||
Phase 2: Required Fields ✅
|
||||
Required: 5/5 present
|
||||
Recommended: 3/4 present
|
||||
Missing: category (non-critical)
|
||||
|
||||
Phase 3: Format Compliance ✅
|
||||
Checks: 7/7 passed
|
||||
Version: 1.0.0 (valid semver)
|
||||
Name: my-plugin (valid lowercase-hyphen)
|
||||
License: MIT (valid SPDX)
|
||||
URLs: All valid HTTPS
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
VALIDATION SUMMARY
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Overall Status: ✅ PASS
|
||||
|
||||
Checks Performed: 15
|
||||
Critical Errors: 0
|
||||
Warnings: 1
|
||||
Passed: 14
|
||||
|
||||
Publication Readiness: READY ✅
|
||||
Your plugin meets all required standards
|
||||
Consider adding: category field for better discoverability
|
||||
|
||||
Quality Score: 95/100 ⭐⭐⭐⭐⭐
|
||||
```
|
||||
|
||||
**Failure (multiple phases fail)**:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
FULL SCHEMA VALIDATION
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Target: plugin.json
|
||||
Type: plugin
|
||||
Mode: Standard
|
||||
|
||||
Phase 1: JSON Syntax ✅
|
||||
Status: Valid JSON
|
||||
Backend: python3
|
||||
|
||||
Phase 2: Required Fields ❌
|
||||
Required: 3/5 present
|
||||
Missing:
|
||||
❌ version (REQUIRED - use semver X.Y.Z)
|
||||
❌ license (REQUIRED - use MIT, Apache-2.0, etc.)
|
||||
|
||||
Phase 3: Format Compliance ❌
|
||||
Checks: 4/6 passed
|
||||
Violations:
|
||||
❌ name: "My-Plugin" - must use lowercase-hyphen
|
||||
❌ homepage: "example.com" - must be valid URL
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
VALIDATION SUMMARY
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Overall Status: ❌ FAIL
|
||||
|
||||
Checks Performed: 11
|
||||
Critical Errors: 4
|
||||
Warnings: 0
|
||||
Passed: 7
|
||||
|
||||
Publication Readiness: NOT READY ❌
|
||||
Fix 4 critical issues before submission
|
||||
|
||||
Priority Actions:
|
||||
1. Add version field: "1.0.0"
|
||||
2. Add license field: "MIT"
|
||||
3. Fix name format: "my-plugin"
|
||||
4. Fix homepage URL: "https://example.com"
|
||||
|
||||
Quality Score: 45/100 ⭐⭐
|
||||
Rating: Needs Improvement
|
||||
|
||||
Next Steps:
|
||||
1. Fix all critical errors above
|
||||
2. Re-run validation: /schema-validation full-schema path:.
|
||||
3. Aim for quality score 90+ for publication
|
||||
```
|
||||
|
||||
**Marketplace Example (with plugin entries)**:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
FULL SCHEMA VALIDATION
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Target: marketplace.json
|
||||
Type: marketplace
|
||||
Mode: Standard
|
||||
|
||||
Phase 1: JSON Syntax ✅
|
||||
Status: Valid JSON
|
||||
|
||||
Phase 2: Required Fields ✅
|
||||
Required: 5/5 present
|
||||
Recommended: 4/4 present
|
||||
|
||||
Phase 3: Format Compliance ✅
|
||||
Checks: 4/4 passed
|
||||
|
||||
Phase 4: Plugin Entries ✅
|
||||
Total Entries: 3
|
||||
Passed: 3 (100%)
|
||||
Failed: 0
|
||||
|
||||
Entry 0: code-review ✅
|
||||
Entry 1: deploy-tools ✅
|
||||
Entry 2: security-scan ✅
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
VALIDATION SUMMARY
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Overall Status: ✅ PASS
|
||||
|
||||
Checks Performed: 23
|
||||
Critical Errors: 0
|
||||
Warnings: 0
|
||||
Passed: 23
|
||||
|
||||
Publication Readiness: READY ✅
|
||||
Your marketplace meets all standards
|
||||
All 3 plugin entries are valid
|
||||
|
||||
Quality Score: 100/100 ⭐⭐⭐⭐⭐
|
||||
Rating: Excellent
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **File not found**: List searched paths, suggest creating configuration
|
||||
- **Invalid JSON**: Stop at Phase 1, show syntax errors
|
||||
- **Auto-detect failure**: Suggest specifying type explicitly
|
||||
- **Script execution error**: Show script path and error message
|
||||
|
||||
### Performance
|
||||
|
||||
- **Plugin**: 1-2 seconds (3 phases)
|
||||
- **Marketplace**: 2-5 seconds (4 phases, depends on plugin entry count)
|
||||
- **Large Marketplace**: 5-10 seconds (50+ plugin entries)
|
||||
|
||||
**Request**: $ARGUMENTS
|
||||
76
commands/schema-validation/skill.md
Normal file
76
commands/schema-validation/skill.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
description: Validate JSON schemas, required fields, and format compliance for marketplaces and plugins
|
||||
---
|
||||
|
||||
You are the Schema Validation coordinator, ensuring structural integrity and format compliance.
|
||||
|
||||
## Your Mission
|
||||
|
||||
Parse `$ARGUMENTS` to determine the requested schema validation operation and route to the appropriate sub-command.
|
||||
|
||||
## Available Operations
|
||||
|
||||
Parse the first word of `$ARGUMENTS` to determine which operation to execute:
|
||||
|
||||
- **json** → Read `.claude/commands/schema-validation/validate-json.md`
|
||||
- **fields** → Read `.claude/commands/schema-validation/check-required-fields.md`
|
||||
- **formats** → Read `.claude/commands/schema-validation/validate-formats.md`
|
||||
- **entries** → Read `.claude/commands/schema-validation/check-plugin-entries.md`
|
||||
- **full-schema** → Read `.claude/commands/schema-validation/full-schema-validation.md`
|
||||
|
||||
## Argument Format
|
||||
|
||||
```
|
||||
/schema-validation <operation> [parameters]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Validate JSON syntax
|
||||
/schema-validation json file:plugin.json
|
||||
|
||||
# Check required fields
|
||||
/schema-validation fields path:. type:plugin
|
||||
|
||||
# Validate formats (semver, URLs, naming)
|
||||
/schema-validation formats path:.
|
||||
|
||||
# Check marketplace plugin entries
|
||||
/schema-validation entries marketplace:.claude-plugin/marketplace.json
|
||||
|
||||
# Run complete schema validation
|
||||
/schema-validation full-schema path:. type:plugin
|
||||
```
|
||||
|
||||
## Validation Scope
|
||||
|
||||
**For Plugins**:
|
||||
- Required: name, version, description, author, license
|
||||
- Formats: semver (version), lowercase-hyphen (name), valid license
|
||||
- Optional: keywords, category, homepage, repository
|
||||
|
||||
**For Marketplaces**:
|
||||
- Required: name, owner, plugins
|
||||
- Plugin entries: name, version, source, description, author, license
|
||||
- Formats: valid source (github:, URL, path)
|
||||
|
||||
## Error Handling
|
||||
|
||||
If the operation is not recognized:
|
||||
1. List all available operations
|
||||
2. Show validation scope
|
||||
3. Provide usage examples
|
||||
|
||||
## Base Directory
|
||||
|
||||
Base directory for this skill: `.claude/commands/schema-validation/`
|
||||
|
||||
## Your Task
|
||||
|
||||
1. Parse `$ARGUMENTS` to extract operation and parameters
|
||||
2. Read the corresponding operation file
|
||||
3. Execute schema validation with multi-backend support (jq, python3)
|
||||
4. Return detailed validation results with line numbers for errors
|
||||
|
||||
**Current Request**: $ARGUMENTS
|
||||
195
commands/schema-validation/validate-formats.md
Normal file
195
commands/schema-validation/validate-formats.md
Normal file
@@ -0,0 +1,195 @@
|
||||
## Operation: Validate Formats
|
||||
|
||||
Validate format compliance for semver, URLs, email addresses, and naming conventions.
|
||||
|
||||
### Parameters from $ARGUMENTS
|
||||
|
||||
- **path**: Path to plugin directory or marketplace file (required)
|
||||
- **type**: Target type: `plugin` or `marketplace` (optional, auto-detect)
|
||||
- **strict**: Enforce HTTPS for all URLs (optional, default: false)
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Locate Configuration File**
|
||||
```
|
||||
Auto-detect or use specified type:
|
||||
Plugin: plugin.json
|
||||
Marketplace: marketplace.json or .claude-plugin/marketplace.json
|
||||
```
|
||||
|
||||
2. **Execute Format Validation**
|
||||
```
|
||||
Execute .scripts/format-validator.py --file "$config" --type "$type" --strict "$strict"
|
||||
|
||||
Validates:
|
||||
- Semantic versioning (X.Y.Z)
|
||||
- Lowercase-hyphen naming (^[a-z0-9]+(-[a-z0-9]+)*$)
|
||||
- URL formats (http/https)
|
||||
- Email addresses (RFC 5322 compliant)
|
||||
- License identifiers (SPDX)
|
||||
- Category names (10 approved categories)
|
||||
```
|
||||
|
||||
3. **Report Results**
|
||||
```
|
||||
For each field:
|
||||
✅ Valid format
|
||||
❌ Invalid format with specific error and remediation
|
||||
|
||||
Summary:
|
||||
- Total fields checked
|
||||
- Passed count
|
||||
- Failed count
|
||||
- Exit code: 0 (all pass) or 1 (any fail)
|
||||
```
|
||||
|
||||
### Format Validation Rules
|
||||
|
||||
**Semantic Versioning (version field)**:
|
||||
- Pattern: `X.Y.Z` where X, Y, Z are non-negative integers
|
||||
- Valid: `1.0.0`, `2.5.3`, `10.20.30`
|
||||
- Invalid: `1.0`, `v1.0.0`, `1.0.0-beta` (pre-release allowed but optional)
|
||||
|
||||
**Lowercase-Hyphen Naming (name field)**:
|
||||
- Pattern: `^[a-z0-9]+(-[a-z0-9]+)*$`
|
||||
- Valid: `my-plugin`, `test-marketplace`, `plugin123`
|
||||
- Invalid: `My-Plugin`, `test_plugin`, `plugin.name`, `-plugin`, `plugin-`
|
||||
|
||||
**URL Format (homepage, repository fields)**:
|
||||
- Must start with `http://` or `https://`
|
||||
- Strict mode: Only `https://` allowed
|
||||
- Valid: `https://example.com`, `http://localhost:3000`
|
||||
- Invalid: `example.com`, `www.example.com`, `ftp://example.com`
|
||||
|
||||
**Email Format (owner.email, author.email fields)**:
|
||||
- RFC 5322 compliant pattern
|
||||
- Valid: `user@example.com`, `name.surname@company.co.uk`
|
||||
- Invalid: `user@`, `@example.com`, `user example.com`
|
||||
|
||||
**License Identifier (license field)**:
|
||||
- SPDX identifier or "Proprietary"
|
||||
- Common: MIT, Apache-2.0, GPL-3.0, BSD-3-Clause
|
||||
- Valid: `MIT`, `Apache-2.0`, `ISC`, `Proprietary`
|
||||
- Invalid: `mit`, `Apache 2.0`, `BSD`
|
||||
|
||||
**Category (category field)**:
|
||||
- One of 10 approved categories
|
||||
- Valid: development, testing, deployment, documentation, security, database, monitoring, productivity, quality, collaboration
|
||||
- Invalid: coding, devops, tools, utilities
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Validate plugin formats
|
||||
/schema-validation formats path:.
|
||||
|
||||
# Validate marketplace with strict HTTPS enforcement
|
||||
/schema-validation formats path:./test-marketplace type:marketplace strict:true
|
||||
|
||||
# Validate specific plugin
|
||||
/schema-validation formats path:./my-plugin type:plugin
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **File not found**: Show expected locations
|
||||
- **Invalid JSON**: Suggest running json validation first
|
||||
- **Format violation**: Specific error with correct pattern
|
||||
- **Unknown field**: Warn but don't fail
|
||||
|
||||
### Output Format
|
||||
|
||||
**Success (all formats valid)**:
|
||||
```
|
||||
✅ Format Validation: PASS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Target: plugin.json
|
||||
Type: plugin
|
||||
|
||||
Format Checks (7/7):
|
||||
✅ name: "my-plugin" (lowercase-hyphen)
|
||||
✅ version: "1.0.0" (semver)
|
||||
✅ description: Valid length (73 chars)
|
||||
✅ license: "MIT" (SPDX identifier)
|
||||
✅ homepage: "https://example.com" (valid URL)
|
||||
✅ repository: "https://github.com/user/repo" (valid URL)
|
||||
✅ category: "development" (approved category)
|
||||
|
||||
Status: PASS
|
||||
```
|
||||
|
||||
**Failure (format violations)**:
|
||||
```
|
||||
❌ Format Validation: FAIL
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Target: plugin.json
|
||||
Type: plugin
|
||||
|
||||
Format Checks (4/7):
|
||||
❌ name: "My-Plugin"
|
||||
Invalid: Must use lowercase-hyphen format
|
||||
Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
|
||||
Example: my-plugin, test-tool, plugin123
|
||||
|
||||
❌ version: "1.0"
|
||||
Invalid: Must use semantic versioning (X.Y.Z)
|
||||
Expected: Three version numbers separated by dots
|
||||
Example: 1.0.0, 2.1.5
|
||||
|
||||
✅ description: Valid (80 characters)
|
||||
|
||||
❌ license: "Apache 2.0"
|
||||
Invalid: Must be SPDX identifier
|
||||
Expected: Apache-2.0
|
||||
Valid identifiers: MIT, Apache-2.0, GPL-3.0, BSD-3-Clause
|
||||
|
||||
⚠️ homepage: "http://example.com"
|
||||
Warning: Consider using HTTPS for security
|
||||
Current: http://example.com
|
||||
Recommended: https://example.com
|
||||
|
||||
✅ repository: "https://github.com/user/repo"
|
||||
|
||||
❌ category: "coding"
|
||||
Invalid: Must be one of 10 approved categories
|
||||
Valid: development, testing, deployment, documentation,
|
||||
security, database, monitoring, productivity,
|
||||
quality, collaboration
|
||||
|
||||
Failed: 4
|
||||
Warnings: 1
|
||||
Status: FAIL
|
||||
|
||||
Action Required:
|
||||
Fix format violations:
|
||||
- name: Convert to lowercase-hyphen (my-plugin)
|
||||
- version: Use semver format (1.0.0)
|
||||
- license: Use SPDX identifier (Apache-2.0)
|
||||
- category: Choose approved category (development)
|
||||
```
|
||||
|
||||
**Marketplace Example**:
|
||||
```
|
||||
✅ Format Validation: PASS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Target: marketplace.json
|
||||
Type: marketplace
|
||||
|
||||
Format Checks (4/4):
|
||||
✅ name: "enterprise-marketplace" (lowercase-hyphen)
|
||||
✅ owner.email: "devtools@company.com" (valid email)
|
||||
✅ metadata.homepage: "https://company.com/plugins" (valid HTTPS URL)
|
||||
✅ metadata.repository: "https://github.com/company/plugins" (valid HTTPS URL)
|
||||
|
||||
Status: PASS
|
||||
Strict HTTPS: Enforced ✅
|
||||
```
|
||||
|
||||
### Integration
|
||||
|
||||
This operation is called by:
|
||||
- `full-schema-validation.md` - Third validation step after fields check
|
||||
- `best-practices` skill - Naming and versioning validation
|
||||
- Direct user invocation for format checking
|
||||
|
||||
**Request**: $ARGUMENTS
|
||||
110
commands/schema-validation/validate-json.md
Normal file
110
commands/schema-validation/validate-json.md
Normal file
@@ -0,0 +1,110 @@
|
||||
## Operation: Validate JSON Syntax
|
||||
|
||||
Validate JSON file syntax with multi-backend support (jq + python3 fallback).
|
||||
|
||||
### Parameters from $ARGUMENTS
|
||||
|
||||
- **file**: Path to JSON file (required)
|
||||
- **verbose**: Show detailed error information (optional, default: false)
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Parse Arguments**
|
||||
```
|
||||
Extract file path from $ARGUMENTS
|
||||
Check if verbose mode requested
|
||||
```
|
||||
|
||||
2. **Validate File Exists**
|
||||
```
|
||||
IF file does not exist:
|
||||
Return error with file path
|
||||
Exit with status 1
|
||||
```
|
||||
|
||||
3. **Detect JSON Tool**
|
||||
```
|
||||
Execute .scripts/json-validator.py --detect
|
||||
Primary: jq (faster, better error messages)
|
||||
Fallback: python3 (universal availability)
|
||||
```
|
||||
|
||||
4. **Validate JSON Syntax**
|
||||
```
|
||||
Execute .scripts/json-validator.py --file "$file" --verbose "$verbose"
|
||||
|
||||
On success:
|
||||
- Print success message with file path
|
||||
- Return 0
|
||||
|
||||
On failure:
|
||||
- Print error message with line number and details
|
||||
- Show problematic JSON section
|
||||
- Return 1
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Basic JSON validation
|
||||
/schema-validation json file:plugin.json
|
||||
|
||||
# Verbose validation with details
|
||||
/schema-validation json file:marketplace.json verbose:true
|
||||
|
||||
# Validate multiple files (call multiple times)
|
||||
/schema-validation json file:plugin1.json
|
||||
/schema-validation json file:plugin2.json
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **File not found**: Clear message with expected path
|
||||
- **Invalid JSON**: Line number, character position, error description
|
||||
- **No JSON tool available**: Instruction to install jq or python3
|
||||
- **Permission denied**: File access error with remediation
|
||||
|
||||
### Output Format
|
||||
|
||||
**Success**:
|
||||
```
|
||||
✅ Valid JSON: plugin.json
|
||||
Backend: jq
|
||||
```
|
||||
|
||||
**Failure (basic)**:
|
||||
```
|
||||
❌ Invalid JSON: marketplace.json
|
||||
Error: Unexpected token at line 15
|
||||
```
|
||||
|
||||
**Failure (verbose)**:
|
||||
```
|
||||
❌ Invalid JSON: marketplace.json
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Error Details:
|
||||
Line: 15
|
||||
Position: 8
|
||||
Issue: Expected ',' or ']' but got '}'
|
||||
|
||||
Problematic Section (lines 13-17):
|
||||
13 | "plugins": [
|
||||
14 | {
|
||||
15 | "name": "test"
|
||||
16 | }
|
||||
17 | }
|
||||
|
||||
Remediation:
|
||||
- Check for missing commas between array elements
|
||||
- Verify bracket matching: [ ] { }
|
||||
- Use a JSON formatter/linter in your editor
|
||||
```
|
||||
|
||||
### Integration
|
||||
|
||||
This operation is called by:
|
||||
- `full-schema-validation.md` - First validation step
|
||||
- `validation-orchestrator` - Quick validation checks
|
||||
- Direct user invocation for single file checks
|
||||
|
||||
**Request**: $ARGUMENTS
|
||||
Reference in New Issue
Block a user