skeleton-framework 0.1.2__tar.gz → 0.1.4__tar.gz

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.
Files changed (114) hide show
  1. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/PKG-INFO +19 -2
  2. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/README.md +18 -1
  3. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/pyproject.toml +1 -1
  4. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/cli.py +5 -0
  5. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/client.py +3 -1
  6. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/client/init.py +1 -0
  7. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/config.py +151 -0
  8. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/copy.py +393 -0
  9. {skeleton_framework-0.1.2/skeleton/infrastructure/bootstrap/src/templates/app/frontend/src → skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/react}/main.tsx +1 -1
  10. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/react/skeleton-config.json +30 -0
  11. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/react/skeleton-dependencies.json +21 -0
  12. {skeleton_framework-0.1.2/skeleton/infrastructure/bootstrap/src/templates/app/frontend → skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles}/skeleton-config.json +1 -1
  13. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/solid/main.tsx +20 -0
  14. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/solid/skeleton-config.json +30 -0
  15. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/solid/skeleton-dependencies.json +22 -0
  16. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/solid/tsconfig.json +93 -0
  17. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/vue/App.vue +3 -0
  18. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/vue/main.ts +17 -0
  19. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/vue/skeleton-config.json +30 -0
  20. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/vue/skeleton-dependencies.json +22 -0
  21. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/vue/tsconfig.json +91 -0
  22. skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/vue/vue.d.ts +11 -0
  23. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/config.py +3 -3
  24. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/main.py +1 -1
  25. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/main.py +1 -1
  26. skeleton_framework-0.1.4/skeleton/packages/.skeleton/skeleton-cli-1.0.0.tgz +0 -0
  27. skeleton_framework-0.1.4/skeleton/packages/.skeleton/skeleton-webpack-1.0.0.tgz +0 -0
  28. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton_framework.egg-info/PKG-INFO +19 -2
  29. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton_framework.egg-info/SOURCES.txt +15 -3
  30. skeleton_framework-0.1.2/skeleton/infrastructure/bootstrap/src/config.py +0 -54
  31. skeleton_framework-0.1.2/skeleton/infrastructure/bootstrap/src/copy.py +0 -56
  32. skeleton_framework-0.1.2/skeleton/packages/.skeleton/skeleton-cli-1.0.0.tgz +0 -0
  33. skeleton_framework-0.1.2/skeleton/packages/.skeleton/skeleton-webpack-1.0.0.tgz +0 -0
  34. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/LICENSE +0 -0
  35. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/setup.cfg +0 -0
  36. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/__init__.py +0 -0
  37. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/__init__.py +0 -0
  38. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/__init__.py +0 -0
  39. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/__init__.py +0 -0
  40. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/__init__.py +0 -0
  41. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/client/__init__.py +0 -0
  42. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/client/install.py +0 -0
  43. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/dev/__init__.py +0 -0
  44. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/dev/build.py +0 -0
  45. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/dev/init.py +0 -0
  46. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/dev/install.py +0 -0
  47. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/commands/dev/pack.py +0 -0
  48. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/load.py +0 -0
  49. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/backend/requirements.txt +0 -0
  50. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/backend/routes.json +0 -0
  51. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/backend/src/__init__.py +0 -0
  52. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/backend/src/main.py +0 -0
  53. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/frontend/package.json +0 -0
  54. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/index.html +0 -0
  55. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/css/styles.css +0 -0
  56. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/img/skeleton.png +0 -0
  57. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/js/script.js +0 -0
  58. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/config/.gitignore +0 -0
  59. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/config/skeleton-dependencies.json +0 -0
  60. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/config/skeleton-layout.json +0 -0
  61. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/config/skeleton-schemas.json +0 -0
  62. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/bootstrap/src/templates/config/skeleton-servers.json +0 -0
  63. {skeleton_framework-0.1.2/skeleton/infrastructure/bootstrap/src/templates/app/frontend → skeleton_framework-0.1.4/skeleton/infrastructure/bootstrap/src/templates/profiles/react}/tsconfig.json +0 -0
  64. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/__init__.py +0 -0
  65. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/config.json +0 -0
  66. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/__init__.py +0 -0
  67. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/constants.py +0 -0
  68. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/context.py +0 -0
  69. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/env.py +0 -0
  70. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/layout.py +0 -0
  71. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/loader.py +0 -0
  72. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/repository.py +0 -0
  73. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/common/src/utils.py +0 -0
  74. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/__init__.py +0 -0
  75. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/settings/proxy.json +0 -0
  76. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/settings/services.json +0 -0
  77. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/__init__.py +0 -0
  78. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/app.py +0 -0
  79. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/client.py +0 -0
  80. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/config.py +0 -0
  81. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/context.py +0 -0
  82. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/headers.py +0 -0
  83. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/lifespan.py +0 -0
  84. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/loader.py +0 -0
  85. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/logging.py +0 -0
  86. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/parser.py +0 -0
  87. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/registry.py +0 -0
  88. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/reverseproxy.py +0 -0
  89. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/router.py +0 -0
  90. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/schemas.py +0 -0
  91. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/service.py +0 -0
  92. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/utils.py +0 -0
  93. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/gateway/src/validation.py +0 -0
  94. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/layout.json +0 -0
  95. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/__init__.py +0 -0
  96. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/__init__.py +0 -0
  97. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/actions.py +0 -0
  98. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/browser.py +0 -0
  99. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/cmd.py +0 -0
  100. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/command.py +0 -0
  101. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/frontend.py +0 -0
  102. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/gateway.py +0 -0
  103. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/hook.py +0 -0
  104. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/packages.py +0 -0
  105. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/parser.py +0 -0
  106. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/process.py +0 -0
  107. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/services.py +0 -0
  108. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/utils.py +0 -0
  109. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/infrastructure/runners/src/validation.py +0 -0
  110. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton/root.py +0 -0
  111. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton_framework.egg-info/dependency_links.txt +0 -0
  112. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton_framework.egg-info/entry_points.txt +0 -0
  113. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton_framework.egg-info/requires.txt +0 -0
  114. {skeleton_framework-0.1.2 → skeleton_framework-0.1.4}/skeleton_framework.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skeleton-framework
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A full-stack app framework: Python orchestration + FastAPI + WebPack frontend.
5
5
  Author-email: lambdadotjoburg <lambdadotjoburg@gmail.com>
6
6
  License-Expression: LicenseRef-Proprietary
@@ -107,6 +107,23 @@ Skeleton also exposes the individual stages when you want to configure the appli
107
107
 
108
108
  ### 1. Generate configuration
109
109
 
110
+ Generate the project configuration files before initialization.
111
+ Skeleton supports React and SolidJS frontend profiles.
112
+
113
+ Select the desired frontend framework using the `--config` command:
114
+
115
+ ```bash
116
+ skeleton --config react
117
+ ```
118
+
119
+ Or select SolidJS:
120
+
121
+ ```bash
122
+ skeleton --config solid
123
+ ```
124
+
125
+ If no framework is specified, React is selected by default:
126
+
110
127
  ```bash
111
128
  skeleton --config
112
129
  ```
@@ -400,7 +417,7 @@ source .venv/bin/activate
400
417
  From the test project directory:
401
418
 
402
419
  ```bash
403
- python -m pip install --force-reinstall ../framework/dist/skeleton_framework-0.1.2-py3-none-any.whl
420
+ python -m pip install --force-reinstall ../framework/dist/skeleton_framework-0.1.3-py3-none-any.whl
404
421
  ```
405
422
 
406
423
  Adjust the relative path, wheel filename & version to match your framework checkout and build output.
@@ -89,6 +89,23 @@ Skeleton also exposes the individual stages when you want to configure the appli
89
89
 
90
90
  ### 1. Generate configuration
91
91
 
92
+ Generate the project configuration files before initialization.
93
+ Skeleton supports React and SolidJS frontend profiles.
94
+
95
+ Select the desired frontend framework using the `--config` command:
96
+
97
+ ```bash
98
+ skeleton --config react
99
+ ```
100
+
101
+ Or select SolidJS:
102
+
103
+ ```bash
104
+ skeleton --config solid
105
+ ```
106
+
107
+ If no framework is specified, React is selected by default:
108
+
92
109
  ```bash
93
110
  skeleton --config
94
111
  ```
@@ -382,7 +399,7 @@ source .venv/bin/activate
382
399
  From the test project directory:
383
400
 
384
401
  ```bash
385
- python -m pip install --force-reinstall ../framework/dist/skeleton_framework-0.1.2-py3-none-any.whl
402
+ python -m pip install --force-reinstall ../framework/dist/skeleton_framework-0.1.3-py3-none-any.whl
386
403
  ```
387
404
 
388
405
  Adjust the relative path, wheel filename & version to match your framework checkout and build output.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "skeleton-framework"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "A full-stack app framework: Python orchestration + FastAPI + WebPack frontend."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -27,10 +27,15 @@ from pathlib import Path
27
27
  # ============================================================
28
28
 
29
29
  # skeleton --config
30
+ # skeleton --config react
31
+ # skeleton --config solid
32
+ # skeleton --config vue
33
+
30
34
  # skeleton --init
31
35
  # skeleton --install
32
36
  # skeleton --start
33
37
  # skeleton --start frontend gateway --open
38
+
34
39
  # skeleton --quickstart
35
40
 
36
41
  # skeleton --serve
@@ -176,11 +176,13 @@ def main(
176
176
 
177
177
  from skeleton.infrastructure.bootstrap.src.config import config
178
178
 
179
+ framework = args[1] if len(args) > 1 else None
180
+
179
181
  config(
180
182
  framework_root=framework_root,
181
183
  project_root=project_root,
184
+ framework=framework,
182
185
  )
183
-
184
186
  return 0
185
187
 
186
188
  if command == "--init":
@@ -362,6 +362,7 @@ def init(
362
362
  target=project_root,
363
363
  frontend=layout["frontend"],
364
364
  backend=layout["backend"],
365
+ framework_root=framework_root,
365
366
  )
366
367
 
367
368
  # ---------------------------------------------------------
@@ -0,0 +1,151 @@
1
+ import json, shutil
2
+ from pathlib import Path
3
+
4
+ SUPPORTED_FRAMEWORKS = {"react", "solid", "vue"}
5
+
6
+ def read_json(path: Path) -> dict:
7
+ with path.open("r", encoding="utf-8") as file:
8
+ data = json.load(file)
9
+
10
+ if not isinstance(data, dict):
11
+ raise ValueError(f"Expected a JSON object: {path}")
12
+
13
+ return data
14
+
15
+ def write_json(path: Path, data: dict) -> None:
16
+ with path.open("w", encoding="utf-8") as file:
17
+ json.dump(data, file, indent=4)
18
+ file.write("\n")
19
+
20
+ def config(
21
+ framework_root: Path,
22
+ project_root: Path,
23
+ framework: str | None = None,
24
+ ) -> None:
25
+
26
+ templates_root = (
27
+ framework_root
28
+ / "skeleton"
29
+ / "infrastructure"
30
+ / "bootstrap"
31
+ / "src"
32
+ / "templates"
33
+ )
34
+
35
+ config_templates = templates_root / "config"
36
+ profiles_root = templates_root / "profiles"
37
+
38
+ if not config_templates.is_dir():
39
+ raise FileNotFoundError(
40
+ f"Configuration templates not found: {config_templates}"
41
+ )
42
+
43
+ if not profiles_root.is_dir():
44
+ raise FileNotFoundError(
45
+ f"Framework profiles not found: {profiles_root}"
46
+ )
47
+
48
+ if framework is not None:
49
+ framework = framework.strip().lower()
50
+
51
+ if framework not in SUPPORTED_FRAMEWORKS:
52
+ supported = ", ".join(sorted(SUPPORTED_FRAMEWORKS))
53
+ raise ValueError(
54
+ f"Unsupported frontend framework: {framework!r}. "
55
+ f"Choose one of: {supported}"
56
+ )
57
+
58
+ project_root = project_root.resolve()
59
+ project_root.mkdir(parents=True, exist_ok=True)
60
+
61
+ # ---------------------------------------------------------
62
+ # Copy shared project configuration.
63
+ # ---------------------------------------------------------
64
+
65
+ for template in config_templates.iterdir():
66
+ destination = project_root / template.name
67
+
68
+ if destination.exists():
69
+ print(f"INFO: Preserving existing file: {destination}")
70
+ continue
71
+
72
+ if template.is_dir():
73
+ shutil.copytree(template, destination)
74
+ else:
75
+ shutil.copy2(template, destination)
76
+
77
+ print(f"INFO: Created: {destination}")
78
+
79
+ # ---------------------------------------------------------
80
+ # Determine the selected framework.
81
+ # ---------------------------------------------------------
82
+
83
+ frontend_config_path = project_root / "skeleton-config.json"
84
+ dependencies_path = project_root / "skeleton-dependencies.json"
85
+
86
+ if framework is not None:
87
+ selected_framework = framework
88
+
89
+ elif frontend_config_path.is_file():
90
+ existing_config = read_json(frontend_config_path)
91
+ selected_framework = existing_config.get("framework", "react")
92
+
93
+ if (
94
+ not isinstance(selected_framework, str)
95
+ or selected_framework not in SUPPORTED_FRAMEWORKS
96
+ ):
97
+ raise ValueError(
98
+ f"Invalid framework in {frontend_config_path}: "
99
+ f"{selected_framework!r}"
100
+ )
101
+
102
+ else:
103
+ selected_framework = "react"
104
+
105
+ profile = profiles_root / selected_framework
106
+
107
+ if not profile.is_dir():
108
+ raise FileNotFoundError(
109
+ f"Framework profile not found: {profile}"
110
+ )
111
+
112
+ profile_config_path = profile / "skeleton-config.json"
113
+ profile_dependencies_path = profile / "skeleton-dependencies.json"
114
+
115
+ for path in (profile_config_path, profile_dependencies_path):
116
+ if not path.is_file():
117
+ raise FileNotFoundError(
118
+ f"Required framework profile file not found: {path}"
119
+ )
120
+
121
+ # Read and validate both profile files before writing either.
122
+ frontend_config = read_json(profile_config_path)
123
+ dependencies = read_json(profile_dependencies_path)
124
+
125
+ # Ensure the selected profile identifies itself correctly.
126
+ frontend_config["framework"] = selected_framework
127
+
128
+ # ---------------------------------------------------------
129
+ # Generate the root-level framework configuration.
130
+ # ---------------------------------------------------------
131
+
132
+ # Explicit selection resets both framework-managed files
133
+ # to the chosen profile defaults.
134
+ #
135
+ # Without an explicit selection, preserve existing files
136
+ # and create only files that are missing.
137
+
138
+ if framework is not None or not frontend_config_path.exists():
139
+ write_json(frontend_config_path, frontend_config)
140
+ print(f"INFO: Created/updated: {frontend_config_path}")
141
+ else:
142
+ print(f"INFO: Preserving existing file: {frontend_config_path}")
143
+
144
+ if framework is not None or not dependencies_path.exists():
145
+ write_json(dependencies_path, dependencies)
146
+ print(f"INFO: Created/updated: {dependencies_path}")
147
+ else:
148
+ print(f"INFO: Preserving existing file: {dependencies_path}")
149
+
150
+ print(f"INFO: Selected frontend framework: {selected_framework}")
151
+ print("INFO: Configuration complete")
@@ -0,0 +1,393 @@
1
+ import json
2
+ import shutil
3
+
4
+ from pathlib import Path
5
+
6
+ def copy_template(source: Path, target: Path) -> None:
7
+
8
+ target.mkdir(parents=True, exist_ok=True)
9
+
10
+ for item in source.iterdir():
11
+
12
+ destination = target / item.name
13
+
14
+ if destination.exists():
15
+
16
+ if item.is_dir() and destination.is_dir():
17
+ copy_template(item, destination)
18
+
19
+ continue
20
+
21
+ if item.is_dir():
22
+ shutil.copytree(item, destination)
23
+
24
+ else:
25
+ shutil.copy2(item, destination)
26
+
27
+ def _copy_profile_entry_point(
28
+ framework_root: Path,
29
+ project_root: Path,
30
+ frontend: str,
31
+ config: dict,
32
+ ) -> None:
33
+ """Copy the selected framework's entry point into the client."""
34
+
35
+ framework = config.get("framework")
36
+
37
+ profile_filenames = {
38
+ "react": "main.tsx",
39
+ "solid": "main.tsx",
40
+ "vue": "main.ts",
41
+ }
42
+
43
+ if framework not in profile_filenames:
44
+ raise ValueError(
45
+ f"Unsupported frontend framework: {framework!r}"
46
+ )
47
+
48
+ entry = config.get("entry")
49
+ if not isinstance(entry, dict):
50
+ raise ValueError(
51
+ "skeleton-config.json: 'entry' must be an object."
52
+ )
53
+
54
+ entry_name = entry.get("main")
55
+ if not isinstance(entry_name, str) or not entry_name.strip():
56
+ raise ValueError(
57
+ "skeleton-config.json: 'entry.main' must be "
58
+ "a non-empty string."
59
+ )
60
+
61
+ source_name = profile_filenames[framework]
62
+
63
+ source = (
64
+ framework_root
65
+ / "skeleton"
66
+ / "infrastructure"
67
+ / "bootstrap"
68
+ / "src"
69
+ / "templates"
70
+ / "profiles"
71
+ / framework
72
+ / source_name
73
+ )
74
+
75
+ if not source.is_file():
76
+ raise FileNotFoundError(
77
+ f"Frontend entry-point template not found: {source}"
78
+ )
79
+
80
+ # entry.main is relative to the frontend source directory.
81
+ entry_path = Path(entry_name)
82
+
83
+ if entry_path.is_absolute() or ".." in entry_path.parts:
84
+ raise ValueError(
85
+ "skeleton-config.json: 'entry.main' must be a "
86
+ "relative path within the frontend source directory."
87
+ )
88
+
89
+ # If an extension is supplied, it must match the selected profile.
90
+ if entry_path.suffix and entry_path.suffix != source.suffix:
91
+ raise ValueError(
92
+ f"Entry point {entry_name!r} must use the "
93
+ f"{source.suffix} extension for {framework}."
94
+ )
95
+
96
+ if not entry_path.suffix:
97
+ entry_path = entry_path.with_suffix(source.suffix)
98
+
99
+ config_lib = config.get("lib")
100
+ if not isinstance(config_lib, dict):
101
+ raise ValueError(
102
+ "skeleton-config.json: 'lib' must be an object."
103
+ )
104
+
105
+ source_directory = config_lib.get("src", "src")
106
+ if (
107
+ not isinstance(source_directory, str)
108
+ or not source_directory.strip()
109
+ ):
110
+ raise ValueError(
111
+ "skeleton-config.json: 'lib.src' must be "
112
+ "a non-empty relative path."
113
+ )
114
+
115
+ source_path = Path(source_directory)
116
+ if source_path.is_absolute() or ".." in source_path.parts:
117
+ raise ValueError(
118
+ "skeleton-config.json: 'lib.src' must be a "
119
+ "relative path within the frontend directory."
120
+ )
121
+
122
+ frontend_root = project_root / frontend
123
+ if not frontend_root.is_dir():
124
+ raise FileNotFoundError(
125
+ f"Frontend directory not found: {frontend_root}"
126
+ )
127
+
128
+ destination_directory = frontend_root / source_path
129
+ destination = destination_directory / entry_path
130
+
131
+ # Preserve existing client code on repeated initialization.
132
+ if destination.exists():
133
+ print(
134
+ f"INFO: Preserving existing frontend entry point: "
135
+ f"{destination}"
136
+ )
137
+ return
138
+
139
+ destination_directory.mkdir(
140
+ parents=True,
141
+ exist_ok=True,
142
+ )
143
+
144
+ shutil.copy2(source, destination)
145
+
146
+ print(
147
+ f"INFO: Copied {framework} entry point: {destination}"
148
+ )
149
+
150
+ def _copy_profile_files(
151
+ framework_root: Path,
152
+ project_root: Path,
153
+ frontend: str,
154
+ config: dict,
155
+ ) -> None:
156
+ """Copy additional source files required by the selected framework."""
157
+
158
+ framework = config.get("framework")
159
+
160
+ profile_files = {
161
+ "react": (),
162
+ "solid": (),
163
+ "vue": (
164
+ "App.vue",
165
+ "vue.d.ts",
166
+ ),
167
+ }
168
+
169
+ if framework not in profile_files:
170
+ raise ValueError(
171
+ f"Unsupported frontend framework: {framework!r}"
172
+ )
173
+
174
+ if not profile_files[framework]:
175
+ return
176
+
177
+ config_lib = config.get("lib")
178
+
179
+ if not isinstance(config_lib, dict):
180
+ raise ValueError(
181
+ "skeleton-config.json: 'lib' must be an object."
182
+ )
183
+
184
+ source_directory = config_lib.get("src", "src")
185
+
186
+ if (
187
+ not isinstance(source_directory, str)
188
+ or not source_directory.strip()
189
+ ):
190
+ raise ValueError(
191
+ "skeleton-config.json: 'lib.src' must be "
192
+ "a non-empty relative path."
193
+ )
194
+
195
+ source_path = Path(source_directory)
196
+
197
+ if source_path.is_absolute() or ".." in source_path.parts:
198
+ raise ValueError(
199
+ "skeleton-config.json: 'lib.src' must be a "
200
+ "relative path within the frontend directory."
201
+ )
202
+
203
+ profile = (
204
+ framework_root
205
+ / "skeleton"
206
+ / "infrastructure"
207
+ / "bootstrap"
208
+ / "src"
209
+ / "templates"
210
+ / "profiles"
211
+ / framework
212
+ )
213
+
214
+ destination_directory = (
215
+ project_root
216
+ / frontend
217
+ / source_path
218
+ )
219
+
220
+ destination_directory.mkdir(
221
+ parents=True,
222
+ exist_ok=True,
223
+ )
224
+
225
+ for filename in profile_files[framework]:
226
+
227
+ source = profile / filename
228
+ destination = destination_directory / filename
229
+
230
+ if not source.is_file():
231
+ raise FileNotFoundError(
232
+ f"Frontend profile template not found: {source}"
233
+ )
234
+
235
+ # Preserve client files on repeated initialization.
236
+ if destination.exists():
237
+ print(
238
+ f"INFO: Preserving existing frontend file: "
239
+ f"{destination}"
240
+ )
241
+ continue
242
+
243
+ shutil.copy2(
244
+ source,
245
+ destination,
246
+ )
247
+
248
+ print(
249
+ f"INFO: Copied {framework} profile file: "
250
+ f"{destination}"
251
+ )
252
+
253
+ def copy_app_template(
254
+ source: Path,
255
+ target: Path,
256
+ frontend: str,
257
+ backend: str,
258
+ framework_root: Path,
259
+ ) -> None:
260
+
261
+ target.mkdir(parents=True, exist_ok=True)
262
+
263
+ directories = {
264
+ "frontend": frontend,
265
+ "backend": backend,
266
+ }
267
+
268
+ # ---------------------------------------------------------
269
+ # Copy the application templates.
270
+ # ---------------------------------------------------------
271
+
272
+ for item in source.iterdir():
273
+
274
+ name = directories.get(item.name, item.name)
275
+ destination = target / name
276
+
277
+ if destination.exists():
278
+
279
+ if item.is_dir() and destination.is_dir():
280
+ copy_template(item, destination)
281
+
282
+ continue
283
+
284
+ if item.is_dir():
285
+ shutil.copytree(item, destination)
286
+
287
+ else:
288
+ shutil.copy2(item, destination)
289
+
290
+ # ---------------------------------------------------------
291
+ # Read the selected frontend framework.
292
+ # ---------------------------------------------------------
293
+
294
+ config_path = target / "skeleton-config.json"
295
+
296
+ if not config_path.is_file():
297
+ raise FileNotFoundError(
298
+ f"Skeleton configuration not found: {config_path}. "
299
+ "Run 'skeleton --config' first."
300
+ )
301
+
302
+ with config_path.open(
303
+ "r",
304
+ encoding="utf-8",
305
+ ) as file:
306
+ config = json.load(file)
307
+
308
+ if not isinstance(config, dict):
309
+ raise ValueError(
310
+ f"Expected a JSON object: {config_path}"
311
+ )
312
+
313
+ framework = config.get("framework")
314
+
315
+ supported_frameworks = {
316
+ "react",
317
+ "solid",
318
+ "vue",
319
+ }
320
+
321
+ if framework not in supported_frameworks:
322
+ raise ValueError(
323
+ f"Unsupported frontend framework in {config_path}: "
324
+ f"{framework!r}. Choose one of: "
325
+ f"{', '.join(sorted(supported_frameworks))}"
326
+ )
327
+
328
+ # ---------------------------------------------------------
329
+ # Locate the selected profile's TypeScript configuration.
330
+ # ---------------------------------------------------------
331
+
332
+ profile_tsconfig = (
333
+ framework_root
334
+ / "skeleton"
335
+ / "infrastructure"
336
+ / "bootstrap"
337
+ / "src"
338
+ / "templates"
339
+ / "profiles"
340
+ / framework
341
+ / "tsconfig.json"
342
+ )
343
+
344
+ if not profile_tsconfig.is_file():
345
+ raise FileNotFoundError(
346
+ f"TypeScript configuration not found for "
347
+ f"framework {framework!r}: {profile_tsconfig}"
348
+ )
349
+
350
+ # ---------------------------------------------------------
351
+ # Copy tsconfig.json into the configured frontend directory.
352
+ # ---------------------------------------------------------
353
+
354
+ frontend_root = target / frontend
355
+
356
+ if not frontend_root.is_dir():
357
+ raise FileNotFoundError(
358
+ f"Frontend directory not found: {frontend_root}"
359
+ )
360
+
361
+ destination = frontend_root / "tsconfig.json"
362
+
363
+ shutil.copy2(
364
+ profile_tsconfig,
365
+ destination,
366
+ )
367
+
368
+ print(
369
+ f"INFO: Copied {framework} TypeScript configuration: "
370
+ f"{destination}"
371
+ )
372
+
373
+ # ---------------------------------------------------------
374
+ # Copy the selected profile's entry point.
375
+ # ---------------------------------------------------------
376
+
377
+ _copy_profile_entry_point(
378
+ framework_root=framework_root,
379
+ project_root=target,
380
+ frontend=frontend,
381
+ config=config,
382
+ )
383
+
384
+ # ---------------------------------------------------------
385
+ # Copy additional framework-specific source files.
386
+ # ---------------------------------------------------------
387
+
388
+ _copy_profile_files(
389
+ framework_root=framework_root,
390
+ project_root=target,
391
+ frontend=frontend,
392
+ config=config,
393
+ )
@@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client";
3
3
  const body = document.querySelector("body");
4
4
 
5
5
  if (!body) {
6
- throw new Error("Body element not found");
6
+ throw new Error("<body> element not found");
7
7
  }
8
8
 
9
9
  const root = document.createElement("div");