cox-protocol-package 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/COX_FILE_FORMAT_SPECIFICATION_v1.0.md +40 -200
- package/FINALIZATION.md +50 -0
- package/LocalAppManager/Dashboard/analytics_sidebar_panels/analytics_sidebar_panels.cox +56 -0
- package/LocalAppManager/Dashboard/analytics_sidebar_panels/analytics_sidebar_panels_screenshot.placeholder.txt +1 -0
- package/LocalAppManager/Dashboard/dashboard.cox +62 -0
- package/LocalAppManager/Dashboard/dashboard_screenshot.placeholder.txt +1 -0
- package/LocalAppManager/Dashboard/left_navigation_sidebar/left_navigation_sidebar.cox +56 -0
- package/LocalAppManager/Dashboard/left_navigation_sidebar/left_navigation_sidebar_screenshot.placeholder.txt +1 -0
- package/LocalAppManager/Dashboard/projects_grid_cards/projects_grid_cards.cox +56 -0
- package/LocalAppManager/Dashboard/projects_grid_cards/projects_grid_cards_screenshot.placeholder.txt +1 -0
- package/LocalAppManager/Dashboard/top_header_stats_bar/top_header_stats_bar.cox +56 -0
- package/LocalAppManager/Dashboard/top_header_stats_bar/top_header_stats_bar_screenshot.placeholder.txt +1 -0
- package/MANIFEST.in +20 -0
- package/README.md +67 -154
- package/bin/cox-protocol.js +23 -97
- package/cox_protocol/__init__.py +2 -0
- package/cox_protocol/__main__.py +3 -0
- package/cox_protocol/compiler.py +273 -0
- package/cox_protocol/cox_cli.py +146 -0
- package/cox_protocol/cox_lib.py +599 -0
- package/cox_protocol/package_target.py +262 -0
- package/cox_protocol/react_target.py +306 -0
- package/cox_protocol/scaffold.py +56 -0
- package/cox_protocol/schemas/cox.component.v1.schema.json +180 -0
- package/cox_protocol/schemas/cox.crop_metadata.v1.schema.json +83 -0
- package/cox_protocol/schemas/cox.manifest.v1.schema.json +172 -0
- package/cox_protocol/schemas/cox.page.v1.schema.json +242 -0
- package/cox_protocol/schemas/cox.protocol.v1.schema.json +38 -0
- package/cox_protocol/starter/README.md +7 -0
- package/cox_protocol/starter/manifest.cox +26 -0
- package/cox_protocol/starter/page/action_button/action_button.cox +34 -0
- package/cox_protocol/starter/page/action_button/reference.svg +1 -0
- package/cox_protocol/starter/page/page.cox +39 -0
- package/cox_protocol/starter/page/reference.svg +1 -0
- package/editor-support/vscode/CHANGELOG.md +6 -0
- package/editor-support/vscode/README.md +2 -1
- package/editor-support/vscode/package.json +3 -3
- package/editor-support/vscode/snippets/cox-common.json +1 -1
- package/editor-support/vscode/snippets/cox-component.json +2 -1
- package/editor-support/vscode/snippets/cox-crop-metadata.json +1 -1
- package/editor-support/vscode/snippets/cox-manifest.json +1 -1
- package/editor-support/vscode/snippets/cox-page.json +2 -1
- package/editor-support/vscode/snippets/cox-protocol.json +1 -1
- package/examples/quickstart/README.md +7 -0
- package/examples/quickstart/manifest.cox +26 -0
- package/examples/quickstart/page/action_button/action_button.cox +34 -0
- package/examples/quickstart/page/action_button/reference.svg +1 -0
- package/examples/quickstart/page/page.cox +39 -0
- package/examples/quickstart/page/reference.svg +1 -0
- package/guides/contract-demo.md +24 -0
- package/guides/npm-installation.md +16 -287
- package/manifest.cox +7 -4
- package/package.json +30 -14
- package/protocols/recursive_decomposition_protocol.cox +8 -8
- package/pyproject.toml +17 -6
- package/schemas/cox.component.v1.schema.json +17 -4
- package/schemas/cox.crop_metadata.v1.schema.json +1 -1
- package/schemas/cox.manifest.v1.schema.json +1 -1
- package/schemas/cox.page.v1.schema.json +17 -4
- package/schemas/cox.protocol.v1.schema.json +1 -1
- package/scripts/check_distribution.py +87 -0
- package/scripts/demo.py +62 -0
- package/scripts/package-vscode.mjs +12 -0
- package/scripts/release_check.py +86 -0
- package/tests/conftest.py +21 -0
- package/tests/test_cli_and_typescript.py +123 -0
- package/tests/test_compiler.py +279 -0
- package/tests/test_validation.py +215 -0
- package/tools/__init__.py +2 -2
- package/tools/cox_cli.py +2 -123
- package/tools/cox_lib.py +2 -558
- package/tools/package_target.py +2 -214
- package/tools/react_target.py +2 -278
package/tools/package_target.py
CHANGED
|
@@ -1,215 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
"""Compatibility import for the original source checkout. Use cox_protocol."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import json
|
|
5
|
-
import zipfile
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Iterable
|
|
8
|
-
|
|
9
|
-
from . import __version__
|
|
10
|
-
from .cox_lib import relative_path
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from .cox_lib import ValidationSummary
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
FIXED_ZIP_TIMESTAMP = (1980, 1, 1, 0, 0, 0)
|
|
18
|
-
EXCLUDED_PARTS = {
|
|
19
|
-
".git",
|
|
20
|
-
".mypy_cache",
|
|
21
|
-
".pytest_cache",
|
|
22
|
-
".ruff_cache",
|
|
23
|
-
".venv",
|
|
24
|
-
"__pycache__",
|
|
25
|
-
"node_modules",
|
|
26
|
-
}
|
|
27
|
-
EXCLUDED_SUFFIXES = {".pyc", ".pyo", ".tgz"}
|
|
28
|
-
EXCLUDED_FILENAMES = {
|
|
29
|
-
".DS_Store",
|
|
30
|
-
"cox-guides.zip",
|
|
31
|
-
"cox_protocol_package_compiled.zip",
|
|
32
|
-
"llm_agent_zip_skills_bundle.zip",
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def sha256_bytes(content: bytes) -> str:
|
|
37
|
-
return hashlib.sha256(content).hexdigest()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def sha256_file(path: Path) -> str:
|
|
41
|
-
digest = hashlib.sha256()
|
|
42
|
-
with path.open("rb") as handle:
|
|
43
|
-
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
44
|
-
digest.update(chunk)
|
|
45
|
-
return digest.hexdigest()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def package_filename(summary: "ValidationSummary") -> str:
|
|
49
|
-
manifest = summary.documents[summary.manifest_path]
|
|
50
|
-
raw_name = str(manifest.get("PackageName") or "cox_protocol_package.zip")
|
|
51
|
-
file_name = Path(raw_name).name
|
|
52
|
-
if not file_name.endswith(".zip"):
|
|
53
|
-
file_name = f"{file_name}.zip"
|
|
54
|
-
if file_name == ".zip":
|
|
55
|
-
return "cox_protocol_package.zip"
|
|
56
|
-
return file_name
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def is_safe_project_file(
|
|
60
|
-
path: Path,
|
|
61
|
-
project_root: Path,
|
|
62
|
-
package_output: Path,
|
|
63
|
-
) -> bool:
|
|
64
|
-
resolved = path.resolve()
|
|
65
|
-
try:
|
|
66
|
-
relative = resolved.relative_to(project_root.resolve())
|
|
67
|
-
except ValueError:
|
|
68
|
-
return False
|
|
69
|
-
|
|
70
|
-
if resolved == package_output.resolve():
|
|
71
|
-
return False
|
|
72
|
-
if relative.parts[:2] == ("build", "package"):
|
|
73
|
-
return False
|
|
74
|
-
if any(part in EXCLUDED_PARTS for part in relative.parts):
|
|
75
|
-
return False
|
|
76
|
-
if relative.name in EXCLUDED_FILENAMES:
|
|
77
|
-
return False
|
|
78
|
-
if relative.suffix in EXCLUDED_SUFFIXES:
|
|
79
|
-
return False
|
|
80
|
-
return True
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def iter_files(path: Path) -> Iterable[Path]:
|
|
84
|
-
if path.is_file():
|
|
85
|
-
yield path
|
|
86
|
-
return
|
|
87
|
-
if path.is_dir():
|
|
88
|
-
for child in path.rglob("*"):
|
|
89
|
-
if child.is_file() and not child.is_symlink():
|
|
90
|
-
yield child
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def collect_package_files(
|
|
94
|
-
summary: "ValidationSummary",
|
|
95
|
-
target_results: dict[str, dict[str, Any]],
|
|
96
|
-
package_output: Path,
|
|
97
|
-
) -> list[Path]:
|
|
98
|
-
candidates: list[Path] = [summary.manifest_path, *summary.referenced_paths]
|
|
99
|
-
|
|
100
|
-
for target_name, result in target_results.items():
|
|
101
|
-
if target_name == "package":
|
|
102
|
-
continue
|
|
103
|
-
for key in ("output_root", "manifest"):
|
|
104
|
-
value = result.get(key)
|
|
105
|
-
if isinstance(value, Path):
|
|
106
|
-
candidates.append(value)
|
|
107
|
-
|
|
108
|
-
files_by_relative_path: dict[str, Path] = {}
|
|
109
|
-
for candidate in candidates:
|
|
110
|
-
for file_path in iter_files(candidate):
|
|
111
|
-
if not is_safe_project_file(
|
|
112
|
-
file_path,
|
|
113
|
-
summary.project_root,
|
|
114
|
-
package_output,
|
|
115
|
-
):
|
|
116
|
-
continue
|
|
117
|
-
relative = relative_path(summary.project_root, file_path)
|
|
118
|
-
files_by_relative_path[relative] = file_path.resolve()
|
|
119
|
-
|
|
120
|
-
return [
|
|
121
|
-
files_by_relative_path[key]
|
|
122
|
-
for key in sorted(files_by_relative_path)
|
|
123
|
-
]
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def zip_write_bytes(
|
|
127
|
-
archive: zipfile.ZipFile,
|
|
128
|
-
archive_path: str,
|
|
129
|
-
content: bytes,
|
|
130
|
-
) -> None:
|
|
131
|
-
info = zipfile.ZipInfo(archive_path, FIXED_ZIP_TIMESTAMP)
|
|
132
|
-
info.compress_type = zipfile.ZIP_DEFLATED
|
|
133
|
-
info.external_attr = 0o644 << 16
|
|
134
|
-
archive.writestr(info, content)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def emit_package_archive(
|
|
138
|
-
summary: "ValidationSummary",
|
|
139
|
-
build_root: Path,
|
|
140
|
-
target_results: dict[str, dict[str, Any]],
|
|
141
|
-
) -> dict[str, Any]:
|
|
142
|
-
output_root = build_root / "package"
|
|
143
|
-
output_root.mkdir(parents=True, exist_ok=True)
|
|
144
|
-
|
|
145
|
-
archive_path = output_root / package_filename(summary)
|
|
146
|
-
source_files = collect_package_files(
|
|
147
|
-
summary,
|
|
148
|
-
target_results,
|
|
149
|
-
archive_path,
|
|
150
|
-
)
|
|
151
|
-
archive_root = summary.project_root.name
|
|
152
|
-
|
|
153
|
-
file_records = []
|
|
154
|
-
for source_file in source_files:
|
|
155
|
-
relative = relative_path(summary.project_root, source_file)
|
|
156
|
-
file_records.append(
|
|
157
|
-
{
|
|
158
|
-
"path": f"{archive_root}/{relative}",
|
|
159
|
-
"source": relative,
|
|
160
|
-
"size": source_file.stat().st_size,
|
|
161
|
-
"sha256": sha256_file(source_file),
|
|
162
|
-
}
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
package_manifest = {
|
|
166
|
-
"generatedWith": f"cox {__version__}",
|
|
167
|
-
"deterministic": True,
|
|
168
|
-
"archiveRoot": archive_root,
|
|
169
|
-
"projectName": summary.documents[summary.manifest_path].get(
|
|
170
|
-
"ProjectName"
|
|
171
|
-
),
|
|
172
|
-
"packageName": archive_path.name,
|
|
173
|
-
"fileCount": len(file_records),
|
|
174
|
-
"files": file_records,
|
|
175
|
-
}
|
|
176
|
-
manifest_bytes = (
|
|
177
|
-
json.dumps(package_manifest, indent=2, sort_keys=True) + "\n"
|
|
178
|
-
).encode("utf-8")
|
|
179
|
-
|
|
180
|
-
with zipfile.ZipFile(archive_path, "w") as archive:
|
|
181
|
-
for source_file, file_record in zip(source_files, file_records):
|
|
182
|
-
zip_write_bytes(
|
|
183
|
-
archive,
|
|
184
|
-
str(file_record["path"]),
|
|
185
|
-
source_file.read_bytes(),
|
|
186
|
-
)
|
|
187
|
-
zip_write_bytes(
|
|
188
|
-
archive,
|
|
189
|
-
f"{archive_root}/package.manifest.json",
|
|
190
|
-
manifest_bytes,
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
archive_bytes = archive_path.read_bytes()
|
|
194
|
-
archive_sha256 = sha256_bytes(archive_bytes)
|
|
195
|
-
disk_manifest = {
|
|
196
|
-
**package_manifest,
|
|
197
|
-
"archive": {
|
|
198
|
-
"path": relative_path(summary.project_root, archive_path),
|
|
199
|
-
"size": archive_path.stat().st_size,
|
|
200
|
-
"sha256": archive_sha256,
|
|
201
|
-
},
|
|
202
|
-
}
|
|
203
|
-
manifest_path = output_root / f"{archive_path.stem}.manifest.json"
|
|
204
|
-
manifest_path.write_text(
|
|
205
|
-
json.dumps(disk_manifest, indent=2, sort_keys=True) + "\n",
|
|
206
|
-
encoding="utf-8",
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
"output_root": output_root,
|
|
211
|
-
"archive": archive_path,
|
|
212
|
-
"manifest": manifest_path,
|
|
213
|
-
"file_count": len(file_records) + 1,
|
|
214
|
-
"sha256": archive_sha256,
|
|
215
|
-
}
|
|
3
|
+
from cox_protocol.package_target import * # noqa: F401,F403
|
package/tools/react_target.py
CHANGED
|
@@ -1,279 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
"""Compatibility import for the original source checkout. Use cox_protocol."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import os
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
-
|
|
8
|
-
from . import __version__
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from .cox_lib import ValidationSummary
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def pascal_case(value: str) -> str:
|
|
15
|
-
pieces = value.replace("-", "_").split("_")
|
|
16
|
-
return "".join(piece.capitalize() for piece in pieces if piece)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def relative_import(from_dir: Path, target_dir: Path) -> str:
|
|
20
|
-
import_path = os.path.relpath(target_dir, from_dir).replace(os.sep, "/")
|
|
21
|
-
if import_path.startswith("."):
|
|
22
|
-
return import_path
|
|
23
|
-
return f"./{import_path}"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def document_for_instruction(
|
|
27
|
-
summary: "ValidationSummary",
|
|
28
|
-
instruction_path: str,
|
|
29
|
-
) -> dict[str, Any]:
|
|
30
|
-
document_path = (summary.project_root / instruction_path).resolve()
|
|
31
|
-
try:
|
|
32
|
-
return summary.documents[document_path]
|
|
33
|
-
except KeyError as exc:
|
|
34
|
-
raise RuntimeError(
|
|
35
|
-
f"Missing parsed COX document for {instruction_path}"
|
|
36
|
-
) from exc
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def format_comment_block(lines: list[str]) -> str:
|
|
40
|
-
body = "\n".join(f" * {line}" for line in lines)
|
|
41
|
-
return f"/**\n{body}\n */"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def format_list(items: list[str]) -> str:
|
|
45
|
-
return ", ".join(items) if items else "none"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def write_text(path: Path, content: str) -> None:
|
|
49
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
-
path.write_text(content, encoding="utf-8")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def component_comment_lines(component: dict[str, Any]) -> list[str]:
|
|
54
|
-
visual_style = component.get("VisualStyle", {})
|
|
55
|
-
responsive = component.get("Responsive", {})
|
|
56
|
-
interactions = component.get("Interactions", {})
|
|
57
|
-
hints = component.get("ImplementationHints", [])
|
|
58
|
-
return [
|
|
59
|
-
f"COX component slug: {component.get('Slug', 'unknown')}",
|
|
60
|
-
f"Role: {component.get('Role', 'unspecified')}",
|
|
61
|
-
f"Token refs: {format_list(visual_style.get('Tokens', []))}",
|
|
62
|
-
f"Desktop: {responsive.get('Desktop', 'unspecified')}",
|
|
63
|
-
f"Tablet: {responsive.get('Tablet', 'unspecified')}",
|
|
64
|
-
f"Mobile: {responsive.get('Mobile', 'unspecified')}",
|
|
65
|
-
(
|
|
66
|
-
"Interaction notes: "
|
|
67
|
-
f"{format_list(interactions.get('Inferred', []))}"
|
|
68
|
-
),
|
|
69
|
-
f"Implementation hints: {format_list(hints)}",
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def page_comment_lines(page: dict[str, Any]) -> list[str]:
|
|
74
|
-
layout = page.get("Layout", {})
|
|
75
|
-
regions = page.get("VisibleStructure", {}).get("Regions", [])
|
|
76
|
-
return [
|
|
77
|
-
f"COX page slug: {page.get('Slug', 'unknown')}",
|
|
78
|
-
f"Page type: {page.get('PageType', 'unspecified')}",
|
|
79
|
-
f"Visible regions: {format_list(regions)}",
|
|
80
|
-
f"Desktop layout: {layout.get('Desktop', 'unspecified')}",
|
|
81
|
-
f"Tablet layout: {layout.get('Tablet', 'unspecified')}",
|
|
82
|
-
f"Mobile layout: {layout.get('Mobile', 'unspecified')}",
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def render_component_stub(component: dict[str, Any]) -> str:
|
|
87
|
-
component_name = pascal_case(component["Slug"])
|
|
88
|
-
comment_block = format_comment_block(component_comment_lines(component))
|
|
89
|
-
role = json.dumps(component.get("Role", "Component"))
|
|
90
|
-
lines = [
|
|
91
|
-
'import type { ReactNode } from "react";',
|
|
92
|
-
"",
|
|
93
|
-
comment_block,
|
|
94
|
-
f"export interface {component_name}Props {{",
|
|
95
|
-
" children?: ReactNode;",
|
|
96
|
-
"}",
|
|
97
|
-
"",
|
|
98
|
-
f"export function {component_name}({{",
|
|
99
|
-
" children,",
|
|
100
|
-
f"}}: {component_name}Props) {{",
|
|
101
|
-
" return (",
|
|
102
|
-
(
|
|
103
|
-
" <section "
|
|
104
|
-
f"data-cox-slug=\"{component['Slug']}\" aria-label={role}>"
|
|
105
|
-
),
|
|
106
|
-
(
|
|
107
|
-
" {/* TODO: Replace this structural stub with the real UI "
|
|
108
|
-
"implementation. */}"
|
|
109
|
-
),
|
|
110
|
-
" {children}",
|
|
111
|
-
" </section>",
|
|
112
|
-
" );",
|
|
113
|
-
"}",
|
|
114
|
-
]
|
|
115
|
-
return "\n".join(lines) + "\n"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def render_page_stub(
|
|
119
|
-
page: dict[str, Any],
|
|
120
|
-
component_entries: list[dict[str, Any]],
|
|
121
|
-
react_root: Path,
|
|
122
|
-
) -> str:
|
|
123
|
-
page_name = pascal_case(page["Slug"]) + "Page"
|
|
124
|
-
page_dir = react_root / "pages" / page["ProjectName"] / pascal_case(
|
|
125
|
-
page["Slug"]
|
|
126
|
-
)
|
|
127
|
-
import_lines: list[str] = []
|
|
128
|
-
render_lines: list[str] = []
|
|
129
|
-
|
|
130
|
-
for component_entry in component_entries:
|
|
131
|
-
component_name = pascal_case(component_entry["Slug"])
|
|
132
|
-
component_dir = react_root / "components" / component_name
|
|
133
|
-
import_path = relative_import(page_dir, component_dir)
|
|
134
|
-
import_lines.append(
|
|
135
|
-
f'import {{ {component_name} }} from "{import_path}";'
|
|
136
|
-
)
|
|
137
|
-
render_lines.append(f" <{component_name} />")
|
|
138
|
-
|
|
139
|
-
comment_block = format_comment_block(page_comment_lines(page))
|
|
140
|
-
imports = "\n".join(import_lines)
|
|
141
|
-
rendered_components = "\n".join(render_lines)
|
|
142
|
-
if not rendered_components:
|
|
143
|
-
rendered_components = (
|
|
144
|
-
" {/* TODO: Wire child components from the COX page spec. */}"
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
f"{imports}\n\n"
|
|
149
|
-
f"{comment_block}\n"
|
|
150
|
-
f"export interface {page_name}Props {{}}\n\n"
|
|
151
|
-
f"export function {page_name}(_: {page_name}Props) {{\n"
|
|
152
|
-
" return (\n"
|
|
153
|
-
f" <main data-cox-page=\"{page['Slug']}\">\n"
|
|
154
|
-
f"{rendered_components}\n"
|
|
155
|
-
" </main>\n"
|
|
156
|
-
" );\n"
|
|
157
|
-
"}\n"
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def emit_react_stubs(
|
|
162
|
-
summary: "ValidationSummary",
|
|
163
|
-
build_root: Path,
|
|
164
|
-
) -> dict[str, Any]:
|
|
165
|
-
react_root = build_root / "react"
|
|
166
|
-
component_records: dict[str, dict[str, Any]] = {}
|
|
167
|
-
page_records: list[dict[str, Any]] = []
|
|
168
|
-
|
|
169
|
-
for page_entry in summary.build_index["pages"]:
|
|
170
|
-
page_payload = document_for_instruction(
|
|
171
|
-
summary,
|
|
172
|
-
page_entry["instruction"],
|
|
173
|
-
)
|
|
174
|
-
page_dir = (
|
|
175
|
-
react_root
|
|
176
|
-
/ "pages"
|
|
177
|
-
/ page_payload["ProjectName"]
|
|
178
|
-
/ pascal_case(page_entry["slug"])
|
|
179
|
-
)
|
|
180
|
-
page_output = page_dir / "index.tsx"
|
|
181
|
-
|
|
182
|
-
page_component_entries: list[dict[str, Any]] = []
|
|
183
|
-
for component_entry in page_entry["components"]:
|
|
184
|
-
slug = component_entry["slug"]
|
|
185
|
-
component_payload = document_for_instruction(
|
|
186
|
-
summary,
|
|
187
|
-
component_entry["instruction"],
|
|
188
|
-
)
|
|
189
|
-
component_name = pascal_case(slug)
|
|
190
|
-
component_dir = react_root / "components" / component_name
|
|
191
|
-
component_output = component_dir / "index.tsx"
|
|
192
|
-
if slug not in component_records:
|
|
193
|
-
write_text(
|
|
194
|
-
component_output,
|
|
195
|
-
render_component_stub(component_payload),
|
|
196
|
-
)
|
|
197
|
-
component_records[slug] = {
|
|
198
|
-
"slug": slug,
|
|
199
|
-
"componentName": component_name,
|
|
200
|
-
"output": component_output.relative_to(
|
|
201
|
-
react_root
|
|
202
|
-
).as_posix(),
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
page_component_entries.append(
|
|
206
|
-
{
|
|
207
|
-
"Slug": slug,
|
|
208
|
-
"ComponentName": component_name,
|
|
209
|
-
}
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
write_text(
|
|
213
|
-
page_output,
|
|
214
|
-
render_page_stub(page_payload, page_component_entries, react_root),
|
|
215
|
-
)
|
|
216
|
-
page_records.append(
|
|
217
|
-
{
|
|
218
|
-
"projectName": page_payload["ProjectName"],
|
|
219
|
-
"slug": page_payload["Slug"],
|
|
220
|
-
"componentName": pascal_case(page_payload["Slug"]) + "Page",
|
|
221
|
-
"output": page_output.relative_to(react_root).as_posix(),
|
|
222
|
-
"children": page_component_entries,
|
|
223
|
-
}
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
barrel_lines: list[str] = []
|
|
227
|
-
for component in sorted(
|
|
228
|
-
component_records.values(),
|
|
229
|
-
key=lambda item: item["componentName"],
|
|
230
|
-
):
|
|
231
|
-
component_name = component["componentName"]
|
|
232
|
-
barrel_lines.append(
|
|
233
|
-
(
|
|
234
|
-
f'export {{ {component_name} }} '
|
|
235
|
-
f'from "./components/{component_name}";'
|
|
236
|
-
)
|
|
237
|
-
)
|
|
238
|
-
barrel_lines.append(
|
|
239
|
-
(
|
|
240
|
-
f'export type {{ {component_name}Props }} '
|
|
241
|
-
f'from "./components/{component_name}";'
|
|
242
|
-
)
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
for page in sorted(page_records, key=lambda item: item["componentName"]):
|
|
246
|
-
page_component_name = page["componentName"]
|
|
247
|
-
page_path = page["output"].removesuffix("/index.tsx")
|
|
248
|
-
barrel_lines.append(
|
|
249
|
-
f'export {{ {page_component_name} }} from "./{page_path}";'
|
|
250
|
-
)
|
|
251
|
-
barrel_lines.append(
|
|
252
|
-
(
|
|
253
|
-
f'export type {{ {page_component_name}Props }} '
|
|
254
|
-
f'from "./{page_path}";'
|
|
255
|
-
)
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
write_text(react_root / "index.ts", "\n".join(barrel_lines) + "\n")
|
|
259
|
-
|
|
260
|
-
manifest_payload = {
|
|
261
|
-
"generatedWith": f"cox {__version__}",
|
|
262
|
-
"target": "react",
|
|
263
|
-
"components": sorted(
|
|
264
|
-
component_records.values(),
|
|
265
|
-
key=lambda item: item["componentName"],
|
|
266
|
-
),
|
|
267
|
-
"pages": page_records,
|
|
268
|
-
}
|
|
269
|
-
write_text(
|
|
270
|
-
react_root / "manifest.react.json",
|
|
271
|
-
json.dumps(manifest_payload, indent=2, ensure_ascii=True) + "\n",
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
return {
|
|
275
|
-
"output_root": react_root,
|
|
276
|
-
"component_count": len(component_records),
|
|
277
|
-
"page_count": len(page_records),
|
|
278
|
-
"manifest": react_root / "manifest.react.json",
|
|
279
|
-
}
|
|
3
|
+
from cox_protocol.react_target import * # noqa: F401,F403
|