mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
feat: update component migration orchestrator and add deletion manifest script- Simplify COMPONENT_DIRS to process all TSX components under src/components, excluding specific files.- Introduce EXCLUDED_FILENAMES to filter out non-relevant TSX files.- Enhance documentation for component registry entry format and JSON return structure.- Add a new script to generate a deletion manifest for legacy TSX components with JSON equivalents.
This commit is contained in:
@@ -32,11 +32,9 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_MODEL = os.getenv("CODEX_MODEL") or os.getenv("OPENAI_MODEL") or "gpt-4o-mini"
|
||||
API_CALL_DELAY_SECONDS = 2.0
|
||||
API_CALL_TIMEOUT_SECONDS = 120.0
|
||||
COMPONENT_DIRS = [
|
||||
ROOT / "src" / "components" / "atoms",
|
||||
ROOT / "src" / "components" / "molecules",
|
||||
ROOT / "src" / "components" / "organisms",
|
||||
]
|
||||
# Process all TSX components under src/components, excluding App, index, and test/story files.
|
||||
COMPONENT_DIRS = [ROOT / "src" / "components"]
|
||||
EXCLUDED_FILENAMES = {"app.tsx", "app.jsx", "index.tsx", "index.ts"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -139,16 +137,17 @@ JSON definition schema (src/components/json-definitions/<component>.json):
|
||||
"children": "text" | [{{ ... }}, "..."]
|
||||
}}
|
||||
- Use nested UIComponents in children.
|
||||
- For load.path use the component's folder under src/components: "{component_rel_dir}" (omit the subpath if empty).
|
||||
|
||||
Registry entry format (json-components-registry.json) for the component if missing:
|
||||
{{
|
||||
{{
|
||||
"type": "ComponentName",
|
||||
"name": "ComponentName",
|
||||
"category": "layout|input|display|navigation|feedback|data|custom",
|
||||
"canHaveChildren": true|false,
|
||||
"description": "Short description",
|
||||
"status": "supported",
|
||||
"source": "atoms|molecules|organisms|ui|custom",
|
||||
"source": "atoms|molecules|organisms|ui|custom|misc",
|
||||
"jsonCompatible": true|false,
|
||||
"wrapperRequired": true|false,
|
||||
"load": {{
|
||||
@@ -161,12 +160,12 @@ Registry entry format (json-components-registry.json) for the component if missi
|
||||
"notes": "Optional notes"
|
||||
}}
|
||||
}}
|
||||
Omit optional fields when not applicable.
|
||||
Omit optional fields when not applicable. Use the provided category "{category}" for both category and source when in doubt.
|
||||
|
||||
Return ONLY valid JSON with this shape:
|
||||
{{
|
||||
{{
|
||||
"componentName": "...",
|
||||
"category": "atoms|molecules|organisms",
|
||||
"category": "{category}",
|
||||
"isStateful": true|false,
|
||||
"hook": {{
|
||||
"name": "useComponentName",
|
||||
@@ -206,6 +205,7 @@ Return ONLY valid JSON with this shape:
|
||||
|
||||
Component category: {category}
|
||||
Component path: {path}
|
||||
Component subpath under src/components: {component_rel_dir}
|
||||
|
||||
Existing file contents for diffing:
|
||||
{existing_files}
|
||||
@@ -363,9 +363,18 @@ def list_components(roots: Iterable[Path]) -> List[ComponentTarget]:
|
||||
if not root.exists():
|
||||
continue
|
||||
for path in sorted(root.rglob("*.tsx")):
|
||||
if path.name.lower() in EXCLUDED_FILENAMES:
|
||||
continue
|
||||
if re.search(r"\.(test|spec|stories)\.tsx$", path.name):
|
||||
continue
|
||||
name = path.stem
|
||||
try:
|
||||
rel = path.relative_to(root)
|
||||
category = rel.parts[0] if len(rel.parts) > 1 else path.parent.name
|
||||
except ValueError:
|
||||
category = path.parent.name
|
||||
targets.append(
|
||||
ComponentTarget(name=name, path=path, category=root.name)
|
||||
ComponentTarget(name=name, path=path, category=category)
|
||||
)
|
||||
return targets
|
||||
|
||||
@@ -584,6 +593,13 @@ def run_agent_for_component(
|
||||
) -> Dict[str, Any]:
|
||||
tsx = target.path.read_text(encoding="utf-8")
|
||||
config_file_name = f"{_to_kebab_case(target.name)}.json"
|
||||
components_root = ROOT / "src" / "components"
|
||||
try:
|
||||
rel_dir = str(target.path.parent.relative_to(components_root))
|
||||
except ValueError:
|
||||
rel_dir = str(target.path.parent.relative_to(ROOT))
|
||||
if rel_dir == ".":
|
||||
rel_dir = ""
|
||||
existing_files = {
|
||||
"src/lib/json-ui/json-components.ts": _read_existing_file(
|
||||
out_dir, "src/lib/json-ui/json-components.ts"
|
||||
@@ -610,6 +626,7 @@ def run_agent_for_component(
|
||||
)
|
||||
prompt = PROMPT_TEMPLATE.format(
|
||||
category=target.category,
|
||||
component_rel_dir=rel_dir,
|
||||
path=target.path,
|
||||
tsx=tsx,
|
||||
existing_files=existing_files_blob,
|
||||
|
||||
148
scripts/generate-tsx-deletion-manifest.py
Normal file
148
scripts/generate-tsx-deletion-manifest.py
Normal file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate a deletion manifest for legacy TSX components that have JSON equivalents.
|
||||
|
||||
Heuristics (no dependency on migration-out):
|
||||
- Scan all src/components/**/*.tsx (excluding tests/stories).
|
||||
- Record signals (not required to include):
|
||||
- Matching JSON definition at src/components/json-definitions/<kebab>.json
|
||||
- Matching config page at src/config/pages/<category>/<kebab>.json (only for atoms/molecules/organisms)
|
||||
- Matching registry entry in json-components-registry.json
|
||||
- All TSX files are listed; signals provide confidence for deletion.
|
||||
|
||||
Outputs:
|
||||
- tsxs-to-delete.txt (one relative TSX path per line)
|
||||
- tsxs-to-delete.json (array of {name, category, tsxPath, jsonDefinition, configPage?})
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
COMPONENTS_ROOT = ROOT / "src" / "components"
|
||||
JSON_DEFS_DIR = ROOT / "src" / "components" / "json-definitions"
|
||||
CONFIG_PAGES_DIR = ROOT / "src" / "config" / "pages"
|
||||
REGISTRY_FILE = ROOT / "json-components-registry.json"
|
||||
|
||||
|
||||
def to_kebab(name: str) -> str:
|
||||
if not name:
|
||||
return ""
|
||||
return re.sub(r"([A-Z])", r"-\1", name).lower().lstrip("-")
|
||||
|
||||
|
||||
def to_pascal(name: str) -> str:
|
||||
if not name:
|
||||
return ""
|
||||
parts = re.split(r"[-_\\s]", name)
|
||||
parts = [p for p in parts if p]
|
||||
if len(parts) == 1:
|
||||
s = parts[0]
|
||||
return s[:1].upper() + s[1:]
|
||||
return "".join(p[:1].upper() + p[1:] for p in parts)
|
||||
|
||||
|
||||
def list_tsx_files() -> List[Tuple[str, Path]]:
|
||||
files: List[Tuple[str, Path]] = []
|
||||
if not COMPONENTS_ROOT.exists():
|
||||
return files
|
||||
for path in COMPONENTS_ROOT.rglob("*.tsx"):
|
||||
if ".test." in path.name or ".stories." in path.name:
|
||||
continue
|
||||
parts = path.relative_to(COMPONENTS_ROOT).parts
|
||||
category = parts[0] if parts else "components"
|
||||
files.append((category, path))
|
||||
return files
|
||||
|
||||
|
||||
def load_registry_types() -> Set[str]:
|
||||
if not REGISTRY_FILE.exists():
|
||||
return set()
|
||||
try:
|
||||
data = json.loads(REGISTRY_FILE.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return set()
|
||||
return {c.get("type", "") for c in data.get("components", []) if isinstance(c, dict)}
|
||||
|
||||
|
||||
def matching_json_definition(name: str) -> Optional[Path]:
|
||||
kebab = to_kebab(name)
|
||||
candidate = JSON_DEFS_DIR / f"{kebab}.json"
|
||||
return candidate if candidate.exists() else None
|
||||
|
||||
|
||||
def matching_config_page(category: str, name: str) -> Optional[Path]:
|
||||
kebab = to_kebab(name)
|
||||
candidate = CONFIG_PAGES_DIR / category / f"{kebab}.json"
|
||||
return candidate if candidate.exists() else None
|
||||
|
||||
|
||||
def build_manifest() -> List[Dict[str, str]]:
|
||||
registry_types = {t.lower() for t in load_registry_types() if t}
|
||||
manifest: List[Dict[str, str]] = []
|
||||
for category, tsx_path in list_tsx_files():
|
||||
name = tsx_path.stem
|
||||
kebab = to_kebab(name)
|
||||
pascal = to_pascal(name)
|
||||
signals: Dict[str, str] = {}
|
||||
|
||||
json_def = matching_json_definition(name)
|
||||
if json_def:
|
||||
signals["jsonDefinition"] = str(json_def.relative_to(ROOT))
|
||||
|
||||
config = matching_config_page(category, name)
|
||||
if config:
|
||||
signals["configPage"] = str(config.relative_to(ROOT))
|
||||
|
||||
if pascal.lower() in registry_types:
|
||||
signals["registry"] = "present"
|
||||
|
||||
entry: Dict[str, str] = {
|
||||
"name": name,
|
||||
"category": category,
|
||||
"tsxPath": str(tsx_path.relative_to(ROOT)),
|
||||
**signals,
|
||||
}
|
||||
manifest.append(entry)
|
||||
return manifest
|
||||
|
||||
|
||||
def write_manifest(entries: List[Dict[str, str]]) -> None:
|
||||
out_dir = ROOT
|
||||
txt_path = out_dir / "tsxs-to-delete.txt"
|
||||
json_path = out_dir / "tsxs-to-delete.json"
|
||||
if not entries:
|
||||
txt_path.write_text("", encoding="utf-8")
|
||||
json_path.write_text("[]\n", encoding="utf-8")
|
||||
print("No TSX files matched the deletion criteria.")
|
||||
return
|
||||
def render_line(e: Dict[str, str]) -> str:
|
||||
parts = [e["tsxPath"]]
|
||||
arrows: List[str] = []
|
||||
if "jsonDefinition" in e:
|
||||
arrows.append(f"JSON:{e['jsonDefinition']}")
|
||||
if "configPage" in e:
|
||||
arrows.append(f"CFG:{e['configPage']}")
|
||||
if e.get("registry") == "present":
|
||||
arrows.append("REG:yes")
|
||||
if arrows:
|
||||
parts.append("--> " + " | ".join(arrows))
|
||||
return " ".join(parts)
|
||||
|
||||
txt_path.write_text("\n".join(render_line(e) for e in entries) + "\n", encoding="utf-8")
|
||||
json_path.write_text(json.dumps(entries, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"Wrote {len(entries)} entries to {txt_path}")
|
||||
print(f"Wrote {len(entries)} entries to {json_path}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
entries = build_manifest()
|
||||
write_manifest(entries)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user