Fix manifest icon references and add file reference linter
Manifest fixes: - Updated all icon references from .png to .svg files - Changed MIME types from image/png to image/svg+xml - Removed missing screenshots section - Fixed HTML favicon references to use SVG files New reference linter (lint-references.py): - Validates all file references in HTML and manifest files - Checks that referenced files actually exist - Prevents broken links and missing assets - Integrated into justfile as 'validate-references' - Added to check-all command for comprehensive validation Justfile enhancements: - Added validate-references command - Updated check-all to include reference validation - Enhanced validate command with reference checking Documentation updates: - Updated CLAUDE.md with reference validation info - Added file reference checking to code quality standards This prevents issues where manifest/HTML reference non-existent files, improving PWA reliability and catching asset mismatches early. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
82704caa97
commit
35c7d0bdc9
5 changed files with 179 additions and 31 deletions
|
|
@ -68,6 +68,9 @@ bun run check # Check formatting without modifying
|
|||
# Python
|
||||
black server.py # Format Python code
|
||||
pylint server.py # Lint Python code
|
||||
|
||||
# File References
|
||||
just validate-references # Check HTML/manifest file references exist
|
||||
```
|
||||
|
||||
### Icon Generation
|
||||
|
|
@ -164,10 +167,11 @@ node app/create-icons.js # Generate PNG icons
|
|||
- Print styles
|
||||
|
||||
### Code Quality
|
||||
- ESLint: No errors, minimal warnings
|
||||
- ESLint: No errors, no warnings
|
||||
- Prettier: All files formatted
|
||||
- Black: Python code formatted
|
||||
- Pylint: Score 10/10
|
||||
- File References: All HTML/manifest references exist
|
||||
|
||||
## Common Tasks
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
<meta name="theme-color" content="#1a1a2e" />
|
||||
<title>GlitchCraft - Artisanal Text Corruption</title>
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="icons/icon-192.png" />
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="icons/icon-512.png" />
|
||||
<link rel="apple-touch-icon" href="icons/icon-192.png" />
|
||||
<link rel="icon" type="image/svg+xml" sizes="192x192" href="icons/icon-192.svg" />
|
||||
<link rel="icon" type="image/svg+xml" sizes="512x512" href="icons/icon-512.svg" />
|
||||
<link rel="apple-touch-icon" href="icons/icon-192.svg" />
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -9,54 +9,47 @@
|
|||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/icon-72.png",
|
||||
"src": "icons/icon-72.svg",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-96.png",
|
||||
"src": "icons/icon-96.svg",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-128.png",
|
||||
"src": "icons/icon-128.svg",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-144.png",
|
||||
"src": "icons/icon-144.svg",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-152.png",
|
||||
"src": "icons/icon-152.svg",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-192.png",
|
||||
"src": "icons/icon-192.svg",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-384.png",
|
||||
"src": "icons/icon-384.svg",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-512.png",
|
||||
"src": "icons/icon-512.svg",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"categories": ["utilities", "productivity"],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "screenshots/screenshot1.png",
|
||||
"type": "image/png",
|
||||
"sizes": "1280x720"
|
||||
}
|
||||
]
|
||||
"categories": ["utilities", "productivity"]
|
||||
}
|
||||
10
justfile
10
justfile
|
|
@ -55,8 +55,8 @@ lint-all: lint-js lint-python
|
|||
format-all: format-python format
|
||||
@echo "✓ All formatting completed"
|
||||
|
||||
# Check everything (linting + formatting)
|
||||
check-all: lint-all check-format
|
||||
# Check everything (linting + formatting + references)
|
||||
check-all: lint-all check-format validate-references
|
||||
@echo "✓ All checks passed"
|
||||
|
||||
# PWA icon generation
|
||||
|
|
@ -97,8 +97,12 @@ validate-html:
|
|||
validate-manifest:
|
||||
python3 -m json.tool app/manifest.json > /dev/null && echo "✓ Manifest is valid JSON"
|
||||
|
||||
# Check file references (HTML, manifest)
|
||||
validate-references:
|
||||
python3 lint-references.py
|
||||
|
||||
# Run all validations
|
||||
validate: validate-html validate-manifest check-all
|
||||
validate: validate-html validate-manifest validate-references check-all
|
||||
@echo "✓ All validations passed"
|
||||
|
||||
# Git helpers
|
||||
|
|
|
|||
147
lint-references.py
Executable file
147
lint-references.py
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Reference validator for GlitchCraft
|
||||
Checks that all file references in HTML and manifest actually exist
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def check_file_exists(file_path, base_dir):
|
||||
"""Check if a file exists relative to base directory."""
|
||||
full_path = base_dir / file_path
|
||||
return full_path.exists()
|
||||
|
||||
|
||||
def extract_html_references(html_content):
|
||||
"""Extract file references from HTML content."""
|
||||
references = []
|
||||
|
||||
# Link href attributes
|
||||
for match in re.finditer(r'<link[^>]+href=["\']([^"\']+)["\']', html_content):
|
||||
href = match.group(1)
|
||||
if not href.startswith(('http://', 'https://', '//', 'data:', '#')):
|
||||
references.append(href)
|
||||
|
||||
# Script src attributes
|
||||
for match in re.finditer(r'<script[^>]+src=["\']([^"\']+)["\']', html_content):
|
||||
src = match.group(1)
|
||||
if not src.startswith(('http://', 'https://', '//', 'data:')):
|
||||
references.append(src)
|
||||
|
||||
# Image src attributes
|
||||
for match in re.finditer(r'<img[^>]+src=["\']([^"\']+)["\']', html_content):
|
||||
src = match.group(1)
|
||||
if not src.startswith(('http://', 'https://', '//', 'data:')):
|
||||
references.append(src)
|
||||
|
||||
return references
|
||||
|
||||
|
||||
def extract_manifest_references(manifest_data):
|
||||
"""Extract file references from manifest JSON."""
|
||||
references = []
|
||||
|
||||
# Icon sources
|
||||
if 'icons' in manifest_data:
|
||||
for icon in manifest_data['icons']:
|
||||
if 'src' in icon:
|
||||
references.append(icon['src'])
|
||||
|
||||
# Screenshot sources
|
||||
if 'screenshots' in manifest_data:
|
||||
for screenshot in manifest_data['screenshots']:
|
||||
if 'src' in screenshot:
|
||||
references.append(screenshot['src'])
|
||||
|
||||
return references
|
||||
|
||||
|
||||
def check_html_file(file_path, base_dir):
|
||||
"""Check all references in an HTML file."""
|
||||
errors = []
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
references = extract_html_references(content)
|
||||
|
||||
for ref in references:
|
||||
if not check_file_exists(ref, base_dir):
|
||||
errors.append(f"Missing file: {ref}")
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"Error reading {file_path}: {e}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def check_manifest_file(file_path, base_dir):
|
||||
"""Check all references in a manifest file."""
|
||||
errors = []
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
manifest_data = json.load(f)
|
||||
|
||||
references = extract_manifest_references(manifest_data)
|
||||
|
||||
for ref in references:
|
||||
if not check_file_exists(ref, base_dir):
|
||||
errors.append(f"Missing file: {ref}")
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"Error reading {file_path}: {e}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to check all references."""
|
||||
script_dir = Path(__file__).parent
|
||||
app_dir = script_dir / 'app'
|
||||
|
||||
print("🔍 Checking file references...")
|
||||
|
||||
total_errors = 0
|
||||
|
||||
# Check HTML files
|
||||
html_files = list(app_dir.glob('*.html'))
|
||||
for html_file in html_files:
|
||||
errors = check_html_file(html_file, app_dir)
|
||||
if errors:
|
||||
print(f"\n❌ {html_file.name}:")
|
||||
for error in errors:
|
||||
print(f" • {error}")
|
||||
total_errors += 1
|
||||
else:
|
||||
print(f"✅ {html_file.name}")
|
||||
|
||||
# Check manifest file
|
||||
manifest_file = app_dir / 'manifest.json'
|
||||
if manifest_file.exists():
|
||||
errors = check_manifest_file(manifest_file, app_dir)
|
||||
if errors:
|
||||
print(f"\n❌ manifest.json:")
|
||||
for error in errors:
|
||||
print(f" • {error}")
|
||||
total_errors += 1
|
||||
else:
|
||||
print("✅ manifest.json")
|
||||
|
||||
# Summary
|
||||
if total_errors == 0:
|
||||
print(f"\n🎉 All file references are valid!")
|
||||
return 0
|
||||
else:
|
||||
print(f"\n💥 Found {total_errors} reference error(s)")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue