mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-11 19:47:12 +03:00
Squash-merge verified React and Vite expertise at exact head d4fd6cf70d. Required validate and paired evaluation checks passed; advisory droid review had no blocking findings.
77 lines
3.6 KiB
Python
Executable File
77 lines
3.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Bounded, read-only diagnostics for React/Vite projects."""
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
MAX_BYTES = 512 * 1024
|
|
TEXT_FILES = ("package.json", "vite.config.js", "vite.config.ts", "vite.config.mjs", "vite.config.cjs", "tsconfig.json")
|
|
|
|
def read_text(root, name):
|
|
path = root / name
|
|
try:
|
|
if not path.is_file() or path.stat().st_size > MAX_BYTES:
|
|
return None
|
|
return path.read_text(encoding="utf-8")
|
|
except (OSError, UnicodeError):
|
|
return None
|
|
|
|
def diagnose(root):
|
|
result = {"project": str(root), "checks": [], "warnings": []}
|
|
package_text = read_text(root, "package.json")
|
|
package = None
|
|
if package_text is None:
|
|
result["warnings"].append("package.json is missing or exceeds the read limit")
|
|
else:
|
|
try:
|
|
package = json.loads(package_text)
|
|
if not isinstance(package, dict):
|
|
raise ValueError("not an object")
|
|
deps = {**package.get("dependencies", {}), **package.get("devDependencies", {})}
|
|
result["checks"].append({"name": "package-json", "status": "ok"})
|
|
result["checks"].append({"name": "react-dependencies", "status": "ok" if "react" in deps and "react-dom" in deps else "warning"})
|
|
except (ValueError, TypeError, json.JSONDecodeError):
|
|
result["warnings"].append("package.json is not valid JSON")
|
|
config_name = next((name for name in TEXT_FILES[1:5] if read_text(root, name) is not None), None)
|
|
result["checks"].append({"name": "vite-config", "status": "ok" if config_name else "info", "file": config_name})
|
|
source_files = []
|
|
for directory in (root / "src", root / "app"):
|
|
if directory.is_dir():
|
|
source_files.extend(p for p in directory.rglob("*") if p.is_file() and p.suffix in {".jsx", ".tsx", ".js", ".ts"})
|
|
result["checks"].append({"name": "source-entry", "status": "ok" if source_files else "warning", "file_count": len(source_files)})
|
|
env_names = set()
|
|
for path in root.glob(".env*"):
|
|
text = read_text(root, path.name) or ""
|
|
env_names.update(re.findall(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=", text, re.MULTILINE))
|
|
result["checks"].append({"name": "public-env-names", "status": "ok", "names": sorted(n for n in env_names if n.startswith("VITE_"))})
|
|
if any(not n.startswith("VITE_") for n in env_names):
|
|
result["warnings"].append(".env files contain non-public names; keep them server-side and never import secrets into client code")
|
|
lockfiles = [name for name in ("package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock") if (root / name).is_file()]
|
|
result["checks"].append({"name": "lockfile", "status": "ok" if lockfiles else "warning", "files": lockfiles})
|
|
return result
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description="Read-only React/Vite project diagnostics")
|
|
parser.add_argument("project", nargs="?", default=".")
|
|
parser.add_argument("--json", action="store_true", dest="as_json")
|
|
args = parser.parse_args(argv)
|
|
root = Path(args.project).expanduser().resolve()
|
|
if not root.is_dir():
|
|
print("ERROR: project directory does not exist", file=sys.stderr)
|
|
return 2
|
|
result = diagnose(root)
|
|
if args.as_json:
|
|
print(json.dumps(result, sort_keys=True))
|
|
else:
|
|
print("React/Vite doctor: " + result["project"])
|
|
for check in result["checks"]:
|
|
print("- {name}: {status}".format(**check))
|
|
for warning in result["warnings"]:
|
|
print("WARNING: " + warning)
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|