Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:20:28 +08:00
commit b727790a9e
65 changed files with 16412 additions and 0 deletions

View File

@@ -0,0 +1,254 @@
#!/usr/bin/env bash
# ============================================================================
# Category Validator
# ============================================================================
# Purpose: Validate category against OpenPlugins approved category list
# Version: 1.0.0
# Usage: ./category-validator.sh <category> [--suggest]
# Returns: 0=valid, 1=invalid, 2=missing params
# ============================================================================
set -euo pipefail
# OpenPlugins approved categories (exactly 10)
APPROVED_CATEGORIES=(
"development"
"testing"
"deployment"
"documentation"
"security"
"database"
"monitoring"
"productivity"
"quality"
"collaboration"
)
# Category descriptions
declare -A CATEGORY_DESCRIPTIONS=(
["development"]="Code generation, scaffolding, refactoring"
["testing"]="Test generation, coverage, quality assurance"
["deployment"]="CI/CD, infrastructure, release automation"
["documentation"]="Docs generation, API documentation"
["security"]="Vulnerability scanning, secret detection"
["database"]="Schema design, migrations, queries"
["monitoring"]="Performance analysis, logging"
["productivity"]="Workflow automation, task management"
["quality"]="Linting, formatting, code review"
["collaboration"]="Team tools, communication"
)
# ============================================================================
# Functions
# ============================================================================
usage() {
cat <<EOF
Usage: $0 <category> [--suggest]
Validate category against OpenPlugins approved category list.
Arguments:
category Category name to validate (required)
--suggest Show similar categories if invalid
Approved Categories (exactly 10):
1. development - Code generation, scaffolding
2. testing - Test generation, coverage
3. deployment - CI/CD, infrastructure
4. documentation - Docs generation, API docs
5. security - Vulnerability scanning
6. database - Schema design, migrations
7. monitoring - Performance analysis
8. productivity - Workflow automation
9. quality - Linting, formatting
10. collaboration - Team tools, communication
Exit codes:
0 - Valid category
1 - Invalid category
2 - Missing required parameters
EOF
exit 2
}
# Calculate Levenshtein distance for similarity
levenshtein_distance() {
local s1="$1"
local s2="$2"
local len1=${#s1}
local len2=${#s2}
# Simple implementation
if [ "$s1" = "$s2" ]; then
echo 0
return
fi
# Rough approximation: count different characters
local diff=0
local max_len=$((len1 > len2 ? len1 : len2))
for ((i=0; i<max_len; i++)); do
if [ "${s1:i:1}" != "${s2:i:1}" ]; then
((diff++))
fi
done
echo $diff
}
# Find similar categories
find_similar() {
local category="$1"
local suggestions=()
# Check for common misspellings and variations
case "${category,,}" in
*develop*|*dev*)
suggestions+=("development")
;;
*test*)
suggestions+=("testing")
;;
*deploy*|*devops*|*ci*|*cd*)
suggestions+=("deployment")
;;
*doc*|*docs*)
suggestions+=("documentation")
;;
*secur*|*safe*)
suggestions+=("security")
;;
*data*|*db*|*sql*)
suggestions+=("database")
;;
*monitor*|*observ*|*log*)
suggestions+=("monitoring")
;;
*product*|*work*|*auto*)
suggestions+=("productivity")
;;
*qual*|*lint*|*format*)
suggestions+=("quality")
;;
*collab*|*team*|*comm*)
suggestions+=("collaboration")
;;
esac
# If no keyword matches, use similarity
if [ ${#suggestions[@]} -eq 0 ]; then
# Find categories with lowest distance
local best_dist=999
for cat in "${APPROVED_CATEGORIES[@]}"; do
local dist=$(levenshtein_distance "${category,,}" "$cat")
if [ "$dist" -lt "$best_dist" ]; then
best_dist=$dist
suggestions=("$cat")
elif [ "$dist" -eq "$best_dist" ]; then
suggestions+=("$cat")
fi
done
fi
# Remove duplicates
local unique_suggestions=($(printf "%s\n" "${suggestions[@]}" | sort -u))
# Print suggestions
if [ ${#unique_suggestions[@]} -gt 0 ]; then
echo "Did you mean?"
local count=1
for suggestion in "${unique_suggestions[@]}"; do
echo " $count. $suggestion - ${CATEGORY_DESCRIPTIONS[$suggestion]}"
((count++))
done
fi
}
# List all approved categories
list_all_categories() {
cat <<EOF
All Approved Categories:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
local count=1
for category in "${APPROVED_CATEGORIES[@]}"; do
printf "%-2d. %-15s - %s\n" "$count" "$category" "${CATEGORY_DESCRIPTIONS[$category]}"
((count++))
done
}
# ============================================================================
# Main
# ============================================================================
main() {
# Check for help flag
if [ $# -eq 0 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
fi
local category="$1"
local suggest=false
if [ $# -gt 1 ] && [ "$2" = "--suggest" ]; then
suggest=true
fi
# Check if category is provided
if [ -z "$category" ]; then
echo "ERROR: Category cannot be empty"
echo ""
list_all_categories
exit 2
fi
# Normalize to lowercase for comparison
category_lower="${category,,}"
# Check if category is in approved list
for approved in "${APPROVED_CATEGORIES[@]}"; do
if [ "$category_lower" = "$approved" ]; then
echo "✅ PASS: Valid OpenPlugins category"
echo ""
echo "Category: $approved"
echo "Valid: Yes"
echo ""
echo "Description: ${CATEGORY_DESCRIPTIONS[$approved]}"
echo ""
echo "Quality Score Impact: +5 points"
echo ""
echo "The category is approved for OpenPlugins marketplace."
exit 0
fi
done
# Category not found
echo "❌ FAIL: Invalid category"
echo ""
echo "Category: $category"
echo "Valid: No"
echo ""
echo "This category is not in the OpenPlugins approved list."
echo ""
if [ "$suggest" = true ]; then
find_similar "$category"
echo ""
fi
list_all_categories
echo ""
echo "Quality Score Impact: 0 points (fix to gain +5)"
echo ""
echo "Choose the most appropriate category from the approved list."
exit 1
}
main "$@"

View File

@@ -0,0 +1,392 @@
#!/usr/bin/env python3
"""
============================================================================
Keyword Quality Analyzer
============================================================================
Purpose: Analyze keyword quality, count, and relevance for OpenPlugins
Version: 1.0.0
Usage: ./keyword-analyzer.py <keywords> [--min N] [--max N]
Returns: 0=valid, 1=count violation, 2=quality issues, 3=missing params
============================================================================
"""
import sys
import re
from typing import List, Tuple, Dict
# Default constraints
DEFAULT_MIN_KEYWORDS = 3
DEFAULT_MAX_KEYWORDS = 7
# Generic terms to avoid
GENERIC_BLOCKLIST = [
'plugin', 'tool', 'utility', 'helper', 'app',
'code', 'software', 'program', 'system',
'awesome', 'best', 'perfect', 'great', 'super',
'amazing', 'cool', 'nice', 'good', 'excellent'
]
# OpenPlugins categories (should not be duplicated as keywords)
CATEGORIES = [
'development', 'testing', 'deployment', 'documentation',
'security', 'database', 'monitoring', 'productivity',
'quality', 'collaboration'
]
# Common keyword types for balance checking
FUNCTIONALITY_KEYWORDS = [
'testing', 'deployment', 'formatting', 'linting', 'migration',
'generation', 'automation', 'analysis', 'monitoring', 'scanning',
'refactoring', 'debugging', 'profiling', 'optimization'
]
TECHNOLOGY_KEYWORDS = [
'python', 'javascript', 'typescript', 'docker', 'kubernetes',
'react', 'vue', 'angular', 'node', 'bash', 'terraform',
'postgresql', 'mysql', 'redis', 'aws', 'azure', 'gcp'
]
def usage():
"""Print usage information"""
print("""Usage: keyword-analyzer.py <keywords> [--min N] [--max N]
Analyze keyword quality and relevance for OpenPlugins standards.
Arguments:
keywords Comma-separated list of keywords (required)
--min N Minimum keyword count (default: 3)
--max N Maximum keyword count (default: 7)
Requirements:
- Count: 3-7 keywords (optimal: 5-6)
- No generic terms (plugin, tool, awesome)
- No marketing fluff (best, perfect, amazing)
- Mix of functionality and technology
- No redundant variations
Good examples:
"testing,pytest,automation,tdd,python"
"deployment,kubernetes,ci-cd,docker"
"linting,javascript,code-quality"
Bad examples:
"plugin,tool,awesome" (generic)
"test,testing,tests" (redundant)
"development" (only one, too generic)
Exit codes:
0 - Valid keyword set
1 - Count violation (too few or too many)
2 - Quality issues (generic terms, duplicates)
3 - Missing required parameters
""")
sys.exit(3)
def parse_keywords(keyword_string: str) -> List[str]:
"""Parse and normalize keyword string"""
if not keyword_string:
return []
# Split by comma, strip whitespace, lowercase
keywords = [k.strip().lower() for k in keyword_string.split(',')]
# Remove empty strings
keywords = [k for k in keywords if k]
# Remove duplicates while preserving order
seen = set()
unique_keywords = []
for k in keywords:
if k not in seen:
seen.add(k)
unique_keywords.append(k)
return unique_keywords
def check_generic_terms(keywords: List[str]) -> Tuple[List[str], List[str]]:
"""
Check for generic and marketing terms
Returns:
(generic_terms, marketing_terms)
"""
generic_terms = []
marketing_terms = []
for keyword in keywords:
if keyword in GENERIC_BLOCKLIST:
if keyword in ['awesome', 'best', 'perfect', 'great', 'super', 'amazing', 'cool', 'nice', 'good', 'excellent']:
marketing_terms.append(keyword)
else:
generic_terms.append(keyword)
return generic_terms, marketing_terms
def check_redundant_variations(keywords: List[str]) -> List[Tuple[str, str]]:
"""
Find redundant keyword variations
Returns:
List of (keyword1, keyword2) pairs that are redundant
"""
redundant = []
for i, kw1 in enumerate(keywords):
for kw2 in keywords[i+1:]:
# Check if one is a substring of the other
if kw1 in kw2 or kw2 in kw1:
redundant.append((kw1, kw2))
# Check for plural variations
elif kw1.rstrip('s') == kw2 or kw2.rstrip('s') == kw1:
redundant.append((kw1, kw2))
return redundant
def check_category_duplication(keywords: List[str]) -> List[str]:
"""Check if any keywords exactly match category names"""
duplicates = []
for keyword in keywords:
if keyword in CATEGORIES:
duplicates.append(keyword)
return duplicates
def analyze_balance(keywords: List[str]) -> Dict[str, int]:
"""
Analyze keyword balance across types
Returns:
Dict with counts for each type
"""
balance = {
'functionality': 0,
'technology': 0,
'other': 0
}
for keyword in keywords:
if keyword in FUNCTIONALITY_KEYWORDS:
balance['functionality'] += 1
elif keyword in TECHNOLOGY_KEYWORDS:
balance['technology'] += 1
else:
balance['other'] += 1
return balance
def calculate_quality_score(
keywords: List[str],
generic_terms: List[str],
marketing_terms: List[str],
redundant: List[Tuple[str, str]],
category_dups: List[str],
min_count: int,
max_count: int
) -> Tuple[int, List[str]]:
"""
Calculate quality score and list issues
Returns:
(score out of 10, list of issues)
"""
score = 10
issues = []
# Count violations
count = len(keywords)
if count < min_count:
score -= 5
issues.append(f"Too few keywords ({count} < {min_count} minimum)")
elif count > max_count:
score -= 3
issues.append(f"Too many keywords ({count} > {max_count} maximum)")
# Generic terms
if generic_terms:
score -= len(generic_terms) * 2
issues.append(f"Generic terms detected: {', '.join(generic_terms)}")
# Marketing terms
if marketing_terms:
score -= len(marketing_terms) * 2
issues.append(f"Marketing terms detected: {', '.join(marketing_terms)}")
# Redundant variations
if redundant:
score -= len(redundant) * 2
redundant_str = ', '.join([f"{a}/{b}" for a, b in redundant])
issues.append(f"Redundant variations: {redundant_str}")
# Category duplication
if category_dups:
score -= len(category_dups) * 1
issues.append(f"Category name duplication: {', '.join(category_dups)}")
# Single-character keywords
single_char = [k for k in keywords if len(k) == 1]
if single_char:
score -= len(single_char) * 2
issues.append(f"Single-character keywords: {', '.join(single_char)}")
# Balance check
balance = analyze_balance(keywords)
if balance['functionality'] == 0 and balance['technology'] == 0:
score -= 2
issues.append("No functional or technical keywords")
return max(0, score), issues
def suggest_improvements(
keywords: List[str],
generic_terms: List[str],
marketing_terms: List[str],
redundant: List[Tuple[str, str]],
min_count: int,
max_count: int
) -> List[str]:
"""Generate improvement suggestions"""
suggestions = []
# Remove generic/marketing terms
if generic_terms or marketing_terms:
suggestions.append("Remove generic/marketing terms")
suggestions.append(" Replace with specific functionality (e.g., testing, deployment, formatting)")
# Consolidate redundant variations
if redundant:
suggestions.append("Consolidate redundant variations")
for kw1, kw2 in redundant:
suggestions.append(f" Keep one of: {kw1}, {kw2}")
# Add more keywords if too few
count = len(keywords)
if count < min_count:
needed = min_count - count
suggestions.append(f"Add {needed} more relevant keyword(s)")
suggestions.append(" Consider: specific technologies, use-cases, or functionalities")
# Remove keywords if too many
elif count > max_count:
excess = count - max_count
suggestions.append(f"Remove {excess} least relevant keyword(s)")
# Balance suggestions
balance = analyze_balance(keywords)
if balance['functionality'] == 0:
suggestions.append("Add functionality keywords (e.g., testing, automation, deployment)")
if balance['technology'] == 0:
suggestions.append("Add technology keywords (e.g., python, docker, kubernetes)")
return suggestions
def main():
"""Main entry point"""
if len(sys.argv) < 2 or sys.argv[1] in ['-h', '--help']:
usage()
keyword_string = sys.argv[1]
# Parse optional arguments
min_count = DEFAULT_MIN_KEYWORDS
max_count = DEFAULT_MAX_KEYWORDS
for i, arg in enumerate(sys.argv[2:], start=2):
if arg == '--min' and i + 1 < len(sys.argv):
min_count = int(sys.argv[i + 1])
elif arg == '--max' and i + 1 < len(sys.argv):
max_count = int(sys.argv[i + 1])
# Parse keywords
keywords = parse_keywords(keyword_string)
if not keywords:
print("ERROR: Keywords cannot be empty\n")
print("Provide 3-7 relevant keywords describing your plugin.\n")
print("Examples:")
print(' "testing,pytest,automation"')
print(' "deployment,kubernetes,ci-cd"')
sys.exit(3)
# Analyze keywords
count = len(keywords)
generic_terms, marketing_terms = check_generic_terms(keywords)
redundant = check_redundant_variations(keywords)
category_dups = check_category_duplication(keywords)
balance = analyze_balance(keywords)
# Calculate quality score
score, issues = calculate_quality_score(
keywords, generic_terms, marketing_terms,
redundant, category_dups, min_count, max_count
)
# Determine status
if score >= 9 and min_count <= count <= max_count:
status = "✅ PASS"
exit_code = 0
elif count < min_count or count > max_count:
status = "❌ FAIL"
exit_code = 1
elif score < 7:
status = "❌ FAIL"
exit_code = 2
else:
status = "⚠️ WARNING"
exit_code = 0
# Print results
print(f"{status}: Keyword validation\n")
print(f"Keywords: {', '.join(keywords)}")
print(f"Count: {count} (valid range: {min_count}-{max_count})")
print(f"Quality Score: {score}/10\n")
if issues:
print("Issues Found:")
for issue in issues:
print(f" - {issue}")
print()
# Balance breakdown
print("Breakdown:")
print(f" - Functionality: {balance['functionality']} keywords")
print(f" - Technology: {balance['technology']} keywords")
print(f" - Other: {balance['other']} keywords")
print()
# Score impact
if score >= 9:
print("Quality Score Impact: +10 points (excellent)\n")
if exit_code == 0:
print("Excellent keyword selection for discoverability!")
elif score >= 7:
print("Quality Score Impact: +7 points (good)\n")
print("Good keywords, but could be improved.")
else:
print("Quality Score Impact: 0 points (fix to gain +10)\n")
print("Keywords need significant improvement.")
# Suggestions
if issues:
suggestions = suggest_improvements(
keywords, generic_terms, marketing_terms,
redundant, min_count, max_count
)
if suggestions:
print("\nSuggestions:")
for suggestion in suggestions:
print(f" {suggestion}")
sys.exit(exit_code)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,224 @@
#!/usr/bin/env bash
# ============================================================================
# Naming Convention Validator
# ============================================================================
# Purpose: Validate plugin names against OpenPlugins lowercase-hyphen convention
# Version: 1.0.0
# Usage: ./naming-validator.sh <name> [--suggest]
# Returns: 0=valid, 1=invalid, 2=missing params
# ============================================================================
set -euo pipefail
# OpenPlugins naming pattern
NAMING_PATTERN='^[a-z0-9]+(-[a-z0-9]+)*$'
# Generic terms to avoid
GENERIC_TERMS=("plugin" "tool" "utility" "helper" "app" "code" "software")
# ============================================================================
# Functions
# ============================================================================
usage() {
cat <<EOF
Usage: $0 <name> [--suggest]
Validate plugin name against OpenPlugins naming convention.
Arguments:
name Plugin name to validate (required)
--suggest Auto-suggest corrected name if invalid
Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
Valid examples:
- code-formatter
- test-runner
- api-client
Invalid examples:
- Code-Formatter (uppercase)
- test_runner (underscore)
- -helper (leading hyphen)
Exit codes:
0 - Valid naming convention
1 - Invalid naming convention
2 - Missing required parameters
EOF
exit 2
}
# Convert to lowercase-hyphen format
suggest_correction() {
local name="$1"
local corrected="$name"
# Convert to lowercase
corrected="${corrected,,}"
# Replace underscores with hyphens
corrected="${corrected//_/-}"
# Replace spaces with hyphens
corrected="${corrected// /-}"
# Remove non-alphanumeric except hyphens
corrected="$(echo "$corrected" | sed 's/[^a-z0-9-]//g')"
# Remove leading/trailing hyphens
corrected="$(echo "$corrected" | sed 's/^-*//;s/-*$//')"
# Replace multiple consecutive hyphens with single
corrected="$(echo "$corrected" | sed 's/-\+/-/g')"
echo "$corrected"
}
# Check for generic terms
check_generic_terms() {
local name="$1"
local found_generic=()
for term in "${GENERIC_TERMS[@]}"; do
if [[ "$name" == "$term" ]] || [[ "$name" == *"-$term" ]] || [[ "$name" == "$term-"* ]] || [[ "$name" == *"-$term-"* ]]; then
found_generic+=("$term")
fi
done
if [ ${#found_generic[@]} -gt 0 ]; then
echo "Warning: Contains generic term(s): ${found_generic[*]}"
return 1
fi
return 0
}
# Find specific issues in the name
find_issues() {
local name="$1"
local issues=()
# Check for uppercase
if [[ "$name" =~ [A-Z] ]]; then
local uppercase=$(echo "$name" | grep -o '[A-Z]' | tr '\n' ',' | sed 's/,$//')
issues+=("Contains uppercase characters: $uppercase")
fi
# Check for underscores
if [[ "$name" =~ _ ]]; then
issues+=("Contains underscores instead of hyphens")
fi
# Check for spaces
if [[ "$name" =~ \ ]]; then
issues+=("Contains spaces")
fi
# Check for leading hyphen
if [[ "$name" =~ ^- ]]; then
issues+=("Starts with hyphen")
fi
# Check for trailing hyphen
if [[ "$name" =~ -$ ]]; then
issues+=("Ends with hyphen")
fi
# Check for consecutive hyphens
if [[ "$name" =~ -- ]]; then
issues+=("Contains consecutive hyphens")
fi
# Check for special characters
if [[ "$name" =~ [^a-zA-Z0-9_\ -] ]]; then
issues+=("Contains special characters")
fi
# Check for empty or too short
if [ ${#name} -eq 0 ]; then
issues+=("Name is empty")
elif [ ${#name} -eq 1 ]; then
issues+=("Name is too short (single character)")
fi
# Print issues
if [ ${#issues[@]} -gt 0 ]; then
for issue in "${issues[@]}"; do
echo " - $issue"
done
return 1
fi
return 0
}
# ============================================================================
# Main
# ============================================================================
main() {
# Check for help flag
if [ $# -eq 0 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
fi
local name="$1"
local suggest=false
if [ $# -gt 1 ] && [ "$2" = "--suggest" ]; then
suggest=true
fi
# Check if name is provided
if [ -z "$name" ]; then
echo "ERROR: Name cannot be empty"
exit 2
fi
# Validate against pattern
if [[ "$name" =~ $NAMING_PATTERN ]]; then
echo "✅ PASS: Valid naming convention"
echo "Name: $name"
echo "Format: lowercase-hyphen"
# Check for generic terms (warning only)
if ! check_generic_terms "$name"; then
echo ""
echo "Recommendation: Use more descriptive, functionality-specific names"
fi
exit 0
else
echo "❌ FAIL: Invalid naming convention"
echo "Name: $name"
echo ""
echo "Issues Found:"
find_issues "$name"
if [ "$suggest" = true ]; then
local correction=$(suggest_correction "$name")
echo ""
echo "Suggested Correction: $correction"
# Validate the suggestion
if [[ "$correction" =~ $NAMING_PATTERN ]]; then
echo "✓ Suggestion is valid"
else
echo "⚠ Manual correction may be needed"
fi
fi
echo ""
echo "Required Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$"
echo ""
echo "Valid Examples:"
echo " - code-formatter"
echo " - test-runner"
echo " - api-client"
exit 1
fi
}
main "$@"

View File

@@ -0,0 +1,234 @@
#!/usr/bin/env python3
"""
============================================================================
Semantic Version Validator
============================================================================
Purpose: Validate version strings against Semantic Versioning 2.0.0
Version: 1.0.0
Usage: ./semver-checker.py <version> [--strict]
Returns: 0=valid, 1=invalid, 2=missing params, 3=strict mode violation
============================================================================
"""
import re
import sys
from typing import Tuple, Optional, Dict, List
# Semantic versioning patterns
STRICT_SEMVER_PATTERN = r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$'
FULL_SEMVER_PATTERN = r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
def usage():
"""Print usage information"""
print("""Usage: semver-checker.py <version> [--strict]
Validate version string against Semantic Versioning 2.0.0 specification.
Arguments:
version Version string to validate (required)
--strict Enforce strict MAJOR.MINOR.PATCH format (no pre-release/build)
Pattern (strict): MAJOR.MINOR.PATCH (e.g., 1.2.3)
Pattern (full): MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
Valid examples:
- 1.0.0 (strict)
- 1.2.3 (strict)
- 1.0.0-alpha.1 (full)
- 1.2.3+build.20241013 (full)
Invalid examples:
- 1.0 (missing PATCH)
- v1.0.0 (has prefix)
- 1.2.x (placeholder)
Exit codes:
0 - Valid semantic version
1 - Invalid format
2 - Missing required parameters
3 - Strict mode violation (valid semver, but has pre-release/build)
Reference: https://semver.org/
""")
sys.exit(2)
def parse_semver(version: str) -> Optional[Dict[str, any]]:
"""
Parse semantic version string into components
Returns:
Dict with major, minor, patch, prerelease, build
None if invalid format
"""
match = re.match(FULL_SEMVER_PATTERN, version)
if not match:
return None
major, minor, patch, prerelease, build = match.groups()
return {
'major': int(major),
'minor': int(minor),
'patch': int(patch),
'prerelease': prerelease or None,
'build': build or None,
'is_strict': prerelease is None and build is None
}
def find_issues(version: str) -> List[str]:
"""Find specific issues with version format"""
issues = []
# Check for common mistakes
if version.startswith('v') or version.startswith('V'):
issues.append("Starts with 'v' prefix (remove it)")
# Check for missing components
parts = version.split('.')
if len(parts) < 3:
issues.append(f"Missing components (has {len(parts)}, needs 3: MAJOR.MINOR.PATCH)")
elif len(parts) > 3:
# Check if extra parts are pre-release or build
if '-' not in version and '+' not in version:
issues.append(f"Too many components (has {len(parts)}, expected 3)")
# Check for placeholders
if 'x' in version.lower() or '*' in version:
issues.append("Contains placeholder values (x or *)")
# Check for non-numeric base version
base_version = version.split('-')[0].split('+')[0]
base_parts = base_version.split('.')
for i, part in enumerate(base_parts):
if not part.isdigit():
component = ['MAJOR', 'MINOR', 'PATCH'][i] if i < 3 else 'component'
issues.append(f"{component} is not numeric: '{part}'")
# Check for leading zeros
for i, part in enumerate(base_parts[:3]):
if len(part) > 1 and part.startswith('0'):
component = ['MAJOR', 'MINOR', 'PATCH'][i]
issues.append(f"{component} has leading zero: '{part}'")
# Check for non-standard identifiers
if version in ['latest', 'stable', 'dev', 'master', 'main']:
issues.append("Using non-numeric identifier (not a version)")
return issues
def validate_version(version: str, strict: bool = False) -> Tuple[bool, int, str]:
"""
Validate semantic version
Returns:
(is_valid, exit_code, message)
"""
if not version or version.strip() == '':
return False, 2, "ERROR: Version cannot be empty"
# Parse the version
parsed = parse_semver(version)
if parsed is None:
# Invalid format
issues = find_issues(version)
message = "❌ FAIL: Invalid semantic version format\n\n"
message += f"Version: {version}\n"
message += "Valid: No\n\n"
message += "Issues Found:\n"
if issues:
for issue in issues:
message += f" - {issue}\n"
else:
message += " - Does not match semantic versioning pattern\n"
message += "\nRequired Format: MAJOR.MINOR.PATCH\n"
message += "\nExamples:\n"
message += " - 1.0.0 (initial release)\n"
message += " - 1.2.3 (standard version)\n"
message += " - 2.0.0-beta.1 (pre-release)\n"
message += "\nReference: https://semver.org/"
return False, 1, message
# Check strict mode
if strict and not parsed['is_strict']:
message = "⚠️ WARNING: Valid semver, but not strict format\n\n"
message += f"Version: {version}\n"
message += "Format: Valid semver with "
if parsed['prerelease']:
message += "pre-release"
if parsed['build']:
message += " and " if parsed['prerelease'] else ""
message += "build metadata"
message += "\n\n"
message += "Note: OpenPlugins recommends strict MAJOR.MINOR.PATCH format\n"
message += "without pre-release or build metadata for marketplace submissions.\n\n"
message += f"Recommended: {parsed['major']}.{parsed['minor']}.{parsed['patch']} (for stable release)\n\n"
message += "Quality Score Impact: +5 points (valid, but consider strict format)"
return True, 3, message
# Valid version
message = "✅ PASS: Valid semantic version\n\n"
message += f"Version: {version}\n"
message += "Format: "
if parsed['is_strict']:
message += "MAJOR.MINOR.PATCH (strict)\n"
else:
message += "MAJOR.MINOR.PATCH"
if parsed['prerelease']:
message += "-PRERELEASE"
if parsed['build']:
message += "+BUILD"
message += "\n"
message += "Valid: Yes\n\n"
message += "Components:\n"
message += f" - MAJOR: {parsed['major']}"
if parsed['major'] > 0:
message += " (breaking changes)"
message += "\n"
message += f" - MINOR: {parsed['minor']}"
if parsed['minor'] > 0:
message += " (new features)"
message += "\n"
message += f" - PATCH: {parsed['patch']}"
if parsed['patch'] > 0:
message += " (bug fixes)"
message += "\n"
if parsed['prerelease']:
message += f" - Pre-release: {parsed['prerelease']}\n"
if parsed['build']:
message += f" - Build: {parsed['build']}\n"
message += "\n"
if parsed['prerelease']:
message += "Note: Pre-release versions indicate unstable releases.\n"
message += "Remove pre-release identifier for stable marketplace submission.\n\n"
message += "Quality Score Impact: +5 points\n\n"
message += "The version follows Semantic Versioning 2.0.0 specification."
return True, 0, message
def main():
"""Main entry point"""
if len(sys.argv) < 2 or sys.argv[1] in ['-h', '--help']:
usage()
version = sys.argv[1]
strict = '--strict' in sys.argv
is_valid, exit_code, message = validate_version(version, strict)
print(message)
sys.exit(exit_code)
if __name__ == '__main__':
main()