Scaffold Cloudron packaging workspace and automation
This commit is contained in:
96
scripts/lint_repo.py
Executable file
96
scripts/lint_repo.py
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Basic sanity checks for Cloudron packaging scaffolds."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Dict, List
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
EXPECTED_BASE = os.environ.get("CLOUDRON_BASE", "cloudron/base:5.0.0")
|
||||
|
||||
|
||||
def find_apps(apps_dir: pathlib.Path) -> List[pathlib.Path]:
|
||||
return sorted(p for p in apps_dir.iterdir() if p.is_dir())
|
||||
|
||||
|
||||
def check_manifest(app_dir: pathlib.Path) -> List[str]:
|
||||
issues: List[str] = []
|
||||
manifest = app_dir / "CloudronManifest.json"
|
||||
if not manifest.exists():
|
||||
issues.append("missing CloudronManifest.json")
|
||||
return issues
|
||||
try:
|
||||
data = json.loads(manifest.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
issues.append(f"manifest JSON invalid: {exc}")
|
||||
return issues
|
||||
for key in ("id", "title", "version"):
|
||||
if not data.get(key):
|
||||
issues.append(f"manifest missing {key}")
|
||||
tagline = data.get("tagline", "")
|
||||
description = data.get("description", "")
|
||||
if "TODO" in tagline:
|
||||
issues.append("manifest tagline still contains TODO placeholder")
|
||||
if "TODO" in description:
|
||||
issues.append("manifest description still contains TODO placeholder")
|
||||
return issues
|
||||
|
||||
|
||||
def check_dockerfile(app_dir: pathlib.Path) -> List[str]:
|
||||
issues: List[str] = []
|
||||
dockerfile = app_dir / "Dockerfile"
|
||||
if not dockerfile.exists():
|
||||
issues.append("missing Dockerfile")
|
||||
return issues
|
||||
first_from = None
|
||||
for line in dockerfile.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("FROM "):
|
||||
first_from = line.split()[1]
|
||||
break
|
||||
if first_from != EXPECTED_BASE:
|
||||
issues.append(f"Dockerfile base image '{first_from}' != '{EXPECTED_BASE}'")
|
||||
return issues
|
||||
|
||||
|
||||
def check_start_script(app_dir: pathlib.Path) -> List[str]:
|
||||
issues: List[str] = []
|
||||
start = app_dir / "start.sh"
|
||||
if not start.exists():
|
||||
issues.append("missing start.sh")
|
||||
return issues
|
||||
mode = start.stat().st_mode
|
||||
if not mode & 0o111:
|
||||
issues.append("start.sh is not executable")
|
||||
if "Replace start.sh" in start.read_text(encoding="utf-8"):
|
||||
issues.append("start.sh still contains placeholder command")
|
||||
return issues
|
||||
|
||||
|
||||
def main() -> int:
|
||||
apps_dir = ROOT / "apps"
|
||||
if not apps_dir.exists():
|
||||
print("No apps directory present", file=sys.stderr)
|
||||
return 1
|
||||
failures = 0
|
||||
for app_dir in find_apps(apps_dir):
|
||||
app_issues: List[str] = []
|
||||
app_issues.extend(check_manifest(app_dir))
|
||||
app_issues.extend(check_dockerfile(app_dir))
|
||||
app_issues.extend(check_start_script(app_dir))
|
||||
if app_issues:
|
||||
failures += 1
|
||||
print(f"[FAIL] {app_dir.relative_to(ROOT)}")
|
||||
for issue in app_issues:
|
||||
print(f" - {issue}")
|
||||
if failures:
|
||||
print(f"\n{failures} app(s) require updates", file=sys.stderr)
|
||||
return 2
|
||||
print("All apps passed lint checks")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
Reference in New Issue
Block a user