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
|
# Python
|
||||||
black server.py # Format Python code
|
black server.py # Format Python code
|
||||||
pylint server.py # Lint Python code
|
pylint server.py # Lint Python code
|
||||||
|
|
||||||
|
# File References
|
||||||
|
just validate-references # Check HTML/manifest file references exist
|
||||||
```
|
```
|
||||||
|
|
||||||
### Icon Generation
|
### Icon Generation
|
||||||
|
|
@ -164,10 +167,11 @@ node app/create-icons.js # Generate PNG icons
|
||||||
- Print styles
|
- Print styles
|
||||||
|
|
||||||
### Code Quality
|
### Code Quality
|
||||||
- ESLint: No errors, minimal warnings
|
- ESLint: No errors, no warnings
|
||||||
- Prettier: All files formatted
|
- Prettier: All files formatted
|
||||||
- Black: Python code formatted
|
- Black: Python code formatted
|
||||||
- Pylint: Score 10/10
|
- Pylint: Score 10/10
|
||||||
|
- File References: All HTML/manifest references exist
|
||||||
|
|
||||||
## Common Tasks
|
## Common Tasks
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
<meta name="theme-color" content="#1a1a2e" />
|
<meta name="theme-color" content="#1a1a2e" />
|
||||||
<title>GlitchCraft - Artisanal Text Corruption</title>
|
<title>GlitchCraft - Artisanal Text Corruption</title>
|
||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="icons/icon-192.png" />
|
<link rel="icon" type="image/svg+xml" sizes="192x192" href="icons/icon-192.svg" />
|
||||||
<link rel="icon" type="image/png" sizes="512x512" href="icons/icon-512.png" />
|
<link rel="icon" type="image/svg+xml" sizes="512x512" href="icons/icon-512.svg" />
|
||||||
<link rel="apple-touch-icon" href="icons/icon-192.png" />
|
<link rel="apple-touch-icon" href="icons/icon-192.svg" />
|
||||||
<link rel="stylesheet" href="styles.css" />
|
<link rel="stylesheet" href="styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -9,54 +9,47 @@
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "icons/icon-72.png",
|
"src": "icons/icon-72.svg",
|
||||||
"sizes": "72x72",
|
"sizes": "72x72",
|
||||||
"type": "image/png"
|
"type": "image/svg+xml"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/icon-96.png",
|
"src": "icons/icon-96.svg",
|
||||||
"sizes": "96x96",
|
"sizes": "96x96",
|
||||||
"type": "image/png"
|
"type": "image/svg+xml"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/icon-128.png",
|
"src": "icons/icon-128.svg",
|
||||||
"sizes": "128x128",
|
"sizes": "128x128",
|
||||||
"type": "image/png"
|
"type": "image/svg+xml"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/icon-144.png",
|
"src": "icons/icon-144.svg",
|
||||||
"sizes": "144x144",
|
"sizes": "144x144",
|
||||||
"type": "image/png"
|
"type": "image/svg+xml"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/icon-152.png",
|
"src": "icons/icon-152.svg",
|
||||||
"sizes": "152x152",
|
"sizes": "152x152",
|
||||||
"type": "image/png"
|
"type": "image/svg+xml"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/icon-192.png",
|
"src": "icons/icon-192.svg",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png",
|
"type": "image/svg+xml",
|
||||||
"purpose": "any maskable"
|
"purpose": "any maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/icon-384.png",
|
"src": "icons/icon-384.svg",
|
||||||
"sizes": "384x384",
|
"sizes": "384x384",
|
||||||
"type": "image/png"
|
"type": "image/svg+xml"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/icon-512.png",
|
"src": "icons/icon-512.svg",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png",
|
"type": "image/svg+xml",
|
||||||
"purpose": "any maskable"
|
"purpose": "any maskable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"categories": ["utilities", "productivity"],
|
"categories": ["utilities", "productivity"]
|
||||||
"screenshots": [
|
|
||||||
{
|
|
||||||
"src": "screenshots/screenshot1.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "1280x720"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
10
justfile
10
justfile
|
|
@ -55,8 +55,8 @@ lint-all: lint-js lint-python
|
||||||
format-all: format-python format
|
format-all: format-python format
|
||||||
@echo "✓ All formatting completed"
|
@echo "✓ All formatting completed"
|
||||||
|
|
||||||
# Check everything (linting + formatting)
|
# Check everything (linting + formatting + references)
|
||||||
check-all: lint-all check-format
|
check-all: lint-all check-format validate-references
|
||||||
@echo "✓ All checks passed"
|
@echo "✓ All checks passed"
|
||||||
|
|
||||||
# PWA icon generation
|
# PWA icon generation
|
||||||
|
|
@ -97,8 +97,12 @@ validate-html:
|
||||||
validate-manifest:
|
validate-manifest:
|
||||||
python3 -m json.tool app/manifest.json > /dev/null && echo "✓ Manifest is valid JSON"
|
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
|
# Run all validations
|
||||||
validate: validate-html validate-manifest check-all
|
validate: validate-html validate-manifest validate-references check-all
|
||||||
@echo "✓ All validations passed"
|
@echo "✓ All validations passed"
|
||||||
|
|
||||||
# Git helpers
|
# 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