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"
|
DEFAULT_MODEL = os.getenv("CODEX_MODEL") or os.getenv("OPENAI_MODEL") or "gpt-4o-mini"
|
||||||
API_CALL_DELAY_SECONDS = 2.0
|
API_CALL_DELAY_SECONDS = 2.0
|
||||||
API_CALL_TIMEOUT_SECONDS = 120.0
|
API_CALL_TIMEOUT_SECONDS = 120.0
|
||||||
COMPONENT_DIRS = [
|
# Process all TSX components under src/components, excluding App, index, and test/story files.
|
||||||
ROOT / "src" / "components" / "atoms",
|
COMPONENT_DIRS = [ROOT / "src" / "components"]
|
||||||
ROOT / "src" / "components" / "molecules",
|
EXCLUDED_FILENAMES = {"app.tsx", "app.jsx", "index.tsx", "index.ts"}
|
||||||
ROOT / "src" / "components" / "organisms",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -139,16 +137,17 @@ JSON definition schema (src/components/json-definitions/<component>.json):
|
|||||||
"children": "text" | [{{ ... }}, "..."]
|
"children": "text" | [{{ ... }}, "..."]
|
||||||
}}
|
}}
|
||||||
- Use nested UIComponents in children.
|
- 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:
|
Registry entry format (json-components-registry.json) for the component if missing:
|
||||||
{{
|
{{
|
||||||
"type": "ComponentName",
|
"type": "ComponentName",
|
||||||
"name": "ComponentName",
|
"name": "ComponentName",
|
||||||
"category": "layout|input|display|navigation|feedback|data|custom",
|
"category": "layout|input|display|navigation|feedback|data|custom",
|
||||||
"canHaveChildren": true|false,
|
"canHaveChildren": true|false,
|
||||||
"description": "Short description",
|
"description": "Short description",
|
||||||
"status": "supported",
|
"status": "supported",
|
||||||
"source": "atoms|molecules|organisms|ui|custom",
|
"source": "atoms|molecules|organisms|ui|custom|misc",
|
||||||
"jsonCompatible": true|false,
|
"jsonCompatible": true|false,
|
||||||
"wrapperRequired": true|false,
|
"wrapperRequired": true|false,
|
||||||
"load": {{
|
"load": {{
|
||||||
@@ -161,12 +160,12 @@ Registry entry format (json-components-registry.json) for the component if missi
|
|||||||
"notes": "Optional notes"
|
"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:
|
Return ONLY valid JSON with this shape:
|
||||||
{{
|
{{
|
||||||
"componentName": "...",
|
"componentName": "...",
|
||||||
"category": "atoms|molecules|organisms",
|
"category": "{category}",
|
||||||
"isStateful": true|false,
|
"isStateful": true|false,
|
||||||
"hook": {{
|
"hook": {{
|
||||||
"name": "useComponentName",
|
"name": "useComponentName",
|
||||||
@@ -206,6 +205,7 @@ Return ONLY valid JSON with this shape:
|
|||||||
|
|
||||||
Component category: {category}
|
Component category: {category}
|
||||||
Component path: {path}
|
Component path: {path}
|
||||||
|
Component subpath under src/components: {component_rel_dir}
|
||||||
|
|
||||||
Existing file contents for diffing:
|
Existing file contents for diffing:
|
||||||
{existing_files}
|
{existing_files}
|
||||||
@@ -363,9 +363,18 @@ def list_components(roots: Iterable[Path]) -> List[ComponentTarget]:
|
|||||||
if not root.exists():
|
if not root.exists():
|
||||||
continue
|
continue
|
||||||
for path in sorted(root.rglob("*.tsx")):
|
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
|
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(
|
targets.append(
|
||||||
ComponentTarget(name=name, path=path, category=root.name)
|
ComponentTarget(name=name, path=path, category=category)
|
||||||
)
|
)
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
@@ -584,6 +593,13 @@ def run_agent_for_component(
|
|||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
tsx = target.path.read_text(encoding="utf-8")
|
tsx = target.path.read_text(encoding="utf-8")
|
||||||
config_file_name = f"{_to_kebab_case(target.name)}.json"
|
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 = {
|
existing_files = {
|
||||||
"src/lib/json-ui/json-components.ts": _read_existing_file(
|
"src/lib/json-ui/json-components.ts": _read_existing_file(
|
||||||
out_dir, "src/lib/json-ui/json-components.ts"
|
out_dir, "src/lib/json-ui/json-components.ts"
|
||||||
@@ -610,6 +626,7 @@ def run_agent_for_component(
|
|||||||
)
|
)
|
||||||
prompt = PROMPT_TEMPLATE.format(
|
prompt = PROMPT_TEMPLATE.format(
|
||||||
category=target.category,
|
category=target.category,
|
||||||
|
component_rel_dir=rel_dir,
|
||||||
path=target.path,
|
path=target.path,
|
||||||
tsx=tsx,
|
tsx=tsx,
|
||||||
existing_files=existing_files_blob,
|
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