diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ff6c6f4e..33cb82ee 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,11 +4,17 @@ on: push: branches: [main] paths: - - '.github/workflows/lint.yml' + - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' + - '*.yml' + - '*.yaml' pull_request: branches: [main] paths: - - '.github/workflows/lint.yml' + - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' + - '*.yml' + - '*.yaml' jobs: yaml-lint: @@ -17,13 +23,85 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 + - name: Set up Python + uses: actions/setup-python@v5 with: - node-version: "20" + python-version: "3.x" - - name: Install YAML linter - run: npm install -g yaml-lint + - name: Install yamllint + run: pip install yamllint - - name: Lint YAML files - run: yamllint .github/workflows/*.{yml,yaml} + - name: Create yamllint config + run: | + cat > .yamllint.yml << 'EOF' + extends: default + rules: + line-length: + max: 120 + level: warning + comments: + min-spaces-from-content: 1 + braces: + max-spaces-inside: 1 + max-spaces-inside-empty: 0 + brackets: + max-spaces-inside: 1 + max-spaces-inside-empty: 0 + indentation: + spaces: 2 + EOF + + - name: Lint GitHub Actions workflows + run: | + echo "šŸ” Linting GitHub Actions workflows..." + if ! yamllint .github/workflows/; then + echo "āŒ YAML lint errors found in workflows" + echo "::error::YAML formatting issues in GitHub Actions workflows" + exit 1 + fi + echo "āœ… All workflow files passed YAML linting" + + - name: Lint other YAML files + run: | + echo "šŸ” Linting other YAML files..." + # Find all YAML files in repo (excluding node_modules, .git, etc.) + YAML_FILES=$(find . -name "*.yml" -o -name "*.yaml" | grep -v -E '(node_modules|\.git|\.venv|dist)' | grep -v '.yamllint.yml') + + if [ -z "$YAML_FILES" ]; then + echo "ā„¹ļø No additional YAML files found" + exit 0 + fi + + echo "Found YAML files: $YAML_FILES" + + if ! echo "$YAML_FILES" | xargs yamllint; then + echo "āŒ YAML lint errors found in project files" + echo "::error::YAML formatting issues in project files" + exit 1 + fi + echo "āœ… All project YAML files passed linting" + + - name: Validate workflow syntax + run: | + echo "šŸ” Validating GitHub Actions workflow syntax..." + # Use GitHub's own action syntax validation + for file in .github/workflows/*.yml .github/workflows/*.yaml; do + if [[ -f "$file" ]]; then + echo "Validating $file..." + # Basic syntax check - yamllint already validated YAML syntax + # Check for required fields + if ! grep -q "^name:" "$file"; then + echo "::error file=$file::Missing required 'name' field" + exit 1 + fi + if ! grep -q "^on:" "$file"; then + echo "::error file=$file::Missing required 'on' field" + exit 1 + fi + if ! grep -q "^jobs:" "$file"; then + echo "::error file=$file::Missing required 'jobs' field" + exit 1 + fi + fi + done + echo "āœ… All workflows have required structure" diff --git a/.github/workflows/validate-templates.yml b/.github/workflows/validate-templates.yml index 2a6cb619..105e73a5 100644 --- a/.github/workflows/validate-templates.yml +++ b/.github/workflows/validate-templates.yml @@ -176,4 +176,35 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: comment - }); \ No newline at end of file + }); + + - name: Validation Summary + if: always() + run: | + echo "šŸ“Š Template Validation Summary" + echo "=============================" + + # Collect status from all steps + VALIDATION_STATUS="${{ steps.validation.outputs.validation_status || 'not-run' }}" + THIRD_PARTY_FOUND="${{ steps.third_party_check.outputs.third_party_found || 'false' }}" + THUMBNAIL_ISSUES="${{ steps.thumbnail_check.outputs.thumbnail_issues || 'false' }}" + + echo "šŸ” Schema & Structure Validation: $VALIDATION_STATUS" + echo "šŸ“„ JSON Syntax Check: $([ '${{ steps.json-check.outcome }}' = 'success' ] && echo 'passed' || echo 'failed')" + echo "šŸ“ File Size Check: $([ '${{ steps.size-check.outcome }}' = 'success' ] && echo 'completed' || echo 'failed')" + echo "šŸ”§ Third-party Nodes: $([ '$THIRD_PARTY_FOUND' = 'true' ] && echo 'found (warning)' || echo 'clean')" + echo "šŸ–¼ļø Thumbnail Validation: $([ '$THUMBNAIL_ISSUES' = 'true' ] && echo 'issues found' || echo 'passed')" + + # Overall status + if [ "$VALIDATION_STATUS" = "passed" ] && [ "${{ steps.json-check.outcome }}" = "success" ]; then + if [ "$THIRD_PARTY_FOUND" = "true" ] || [ "$THUMBNAIL_ISSUES" = "true" ]; then + echo "::warning title=Validation Complete::āœ… Core validation passed with warnings" + echo "🟔 Overall Status: PASSED WITH WARNINGS" + else + echo "::notice title=Validation Complete::āœ… All validations passed successfully" + echo "🟢 Overall Status: ALL CHECKS PASSED" + fi + else + echo "::error title=Validation Complete::āŒ Validation failed - issues must be resolved" + echo "šŸ”“ Overall Status: FAILED" + fi \ No newline at end of file diff --git a/scripts/validate_templates.py b/scripts/validate_templates.py index d625d279..46337764 100755 --- a/scripts/validate_templates.py +++ b/scripts/validate_templates.py @@ -22,8 +22,8 @@ def load_json(file_path: Path) -> Dict: return json.load(f) -def validate_schema(index_data: List[Dict], schema_path: Path) -> Tuple[bool, List[str]]: - """Validate index.json against JSON schema.""" +def validate_schema(index_data: List[Dict], schema_path: Path, file_name: str = "index.json") -> Tuple[bool, List[str]]: + """Validate index.json against JSON schema with enhanced error reporting.""" errors = [] try: @@ -31,12 +31,31 @@ def validate_schema(index_data: List[Dict], schema_path: Path) -> Tuple[bool, Li jsonschema.validate(instance=index_data, schema=schema) return True, [] except jsonschema.ValidationError as e: - errors.append(f"Schema validation error: {e.message}") - if e.path: - errors.append(f" at path: {'.'.join(str(p) for p in e.path)}") + # Enhanced error reporting for GitHub Actions + path_str = ".".join(str(p) for p in e.path) if e.path else "root" + error_msg = f"Schema validation error at {path_str}: {e.message}" + + # Try to find the specific template name if error is in a template + if e.path and len(e.path) >= 3 and e.path[1] == "templates": + try: + category_idx = e.path[0] + template_idx = e.path[2] + template_name = index_data[category_idx]["templates"][template_idx].get("name", "unknown") + error_msg = f"Template '{template_name}': {e.message}" + except (IndexError, KeyError, TypeError): + pass + + errors.append(error_msg) + + # Add validation context + if hasattr(e, 'absolute_path'): + errors.append(f" Path: {'.'.join(str(p) for p in e.absolute_path)}") + if hasattr(e, 'schema_path'): + errors.append(f" Schema rule: {'.'.join(str(p) for p in e.schema_path)}") + return False, errors except Exception as e: - errors.append(f"Unexpected error during validation: {str(e)}") + errors.append(f"Unexpected error during schema validation: {str(e)}") return False, errors @@ -327,6 +346,9 @@ def main(): all_errors = [] all_warnings = [] + # Check for GitHub Actions environment + is_github_actions = os.environ.get('GITHUB_ACTIONS') == 'true' + # Run validations print("\n1ļøāƒ£ Validating all index files against JSON schema...") schema_all_valid = True @@ -334,18 +356,26 @@ def main(): print(f" Validating {index_file.name}...") try: index_data = load_json(index_file) - valid, errors = validate_schema(index_data, schema_path) + valid, errors = validate_schema(index_data, schema_path, index_file.name) if valid: print(f" āœ… {index_file.name} schema validation passed") + # GitHub Actions annotation for success + if is_github_actions: + print(f"::notice file=templates/{index_file.name}::Schema validation passed") else: print(f" āŒ {index_file.name} schema validation failed") schema_all_valid = False - # Prefix errors with filename for clarity - prefixed_errors = [f"{index_file.name}: {error}" for error in errors] - all_errors.extend(prefixed_errors) + # Enhanced GitHub Actions annotations + for error in errors: + if is_github_actions: + print(f"::error file=templates/{index_file.name}::{error}") + all_errors.append(f"{index_file.name}: {error}") except Exception as e: print(f" āŒ Error loading {index_file.name}: {e}") - all_errors.append(f"{index_file.name}: Failed to load - {e}") + error_msg = f"Failed to load {index_file.name}: {e}" + if is_github_actions: + print(f"::error file=templates/{index_file.name}::{error_msg}") + all_errors.append(error_msg) schema_all_valid = False if schema_all_valid: @@ -388,30 +418,66 @@ def main(): print(" āŒ Templates using deprecated top-level models format found") all_errors.extend(errors) - # Print warnings + # Print warnings with enhanced annotations if all_warnings: - print("\nWarnings:") + print(f"\nWarnings ({len(all_warnings)}):") for warning in all_warnings: print(f" āš ļø {warning}") - # GitHub Actions annotation + # Enhanced GitHub Actions annotation with better categorization if is_github_actions: - print(f"::warning file=templates/index.json::{warning}") + if "not referenced" in warning.lower(): + print(f"::warning file=templates/index.json,title=Orphaned File::{warning}") + elif "missing" in warning.lower(): + print(f"::warning file=templates/index.json,title=Missing File::{warning}") + else: + print(f"::warning file=templates/index.json::{warning}") - # Summary + # Summary with detailed GitHub Actions annotations print("\n" + "="*50) if all_errors: - print(f"āŒ Validation failed with {len(all_errors)} error(s):\n") - for error in all_errors: - print(f" • {error}") - # GitHub Actions annotation for errors - if is_github_actions: - print(f"::error file=templates/index.json::{error}") + print(f"āŒ Validation failed with {len(all_errors)} error(s):") + + # Categorize errors for better reporting + schema_errors = [e for e in all_errors if "schema" in e.lower()] + file_errors = [e for e in all_errors if "not found" in e.lower() or "missing" in e.lower()] + other_errors = [e for e in all_errors if e not in schema_errors and e not in file_errors] + + if schema_errors: + print(f"\nšŸ“‹ Schema Errors ({len(schema_errors)}):") + for error in schema_errors: + print(f" • {error}") + + if file_errors: + print(f"\nšŸ“ File Errors ({len(file_errors)}):") + for error in file_errors: + print(f" • {error}") + + if other_errors: + print(f"\nšŸ”§ Other Errors ({len(other_errors)}):") + for error in other_errors: + print(f" • {error}") + + # GitHub Actions summary annotation + if is_github_actions: + error_summary = f"Validation failed: {len(schema_errors)} schema, {len(file_errors)} file, {len(other_errors)} other errors" + print(f"::error title=Validation Failed::{error_summary}") + return 1 else: + success_msg = f"āœ… All validations passed!" if all_warnings: - print(f"āœ… All validations passed with {len(all_warnings)} warning(s)!") - else: - print("āœ… All validations passed!") + success_msg = f"āœ… All validations passed with {len(all_warnings)} warning(s)!" + + print(success_msg) + + # GitHub Actions success annotation + if is_github_actions: + files_validated = len(index_files) + summary = f"Successfully validated {files_validated} index files" + if all_warnings: + summary += f" with {len(all_warnings)} warnings" + print(f"::notice title=Validation Success::{summary}") + return 0