skeleton-framework 0.1.0__py3-none-any.whl

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 (93) hide show
  1. skeleton/__init__.py +0 -0
  2. skeleton/cli.py +151 -0
  3. skeleton/client.py +269 -0
  4. skeleton/infrastructure/__init__.py +1 -0
  5. skeleton/infrastructure/bootstrap/__init__.py +0 -0
  6. skeleton/infrastructure/bootstrap/src/__init__.py +0 -0
  7. skeleton/infrastructure/bootstrap/src/commands/__init__.py +0 -0
  8. skeleton/infrastructure/bootstrap/src/commands/client/__init__.py +0 -0
  9. skeleton/infrastructure/bootstrap/src/commands/client/init.py +486 -0
  10. skeleton/infrastructure/bootstrap/src/commands/client/install.py +113 -0
  11. skeleton/infrastructure/bootstrap/src/commands/dev/__init__.py +0 -0
  12. skeleton/infrastructure/bootstrap/src/commands/dev/build.py +80 -0
  13. skeleton/infrastructure/bootstrap/src/commands/dev/init.py +80 -0
  14. skeleton/infrastructure/bootstrap/src/commands/dev/install.py +168 -0
  15. skeleton/infrastructure/bootstrap/src/commands/dev/pack.py +55 -0
  16. skeleton/infrastructure/bootstrap/src/config.py +54 -0
  17. skeleton/infrastructure/bootstrap/src/copy.py +56 -0
  18. skeleton/infrastructure/bootstrap/src/load.py +221 -0
  19. skeleton/infrastructure/bootstrap/src/templates/app/backend/requirements.txt +1 -0
  20. skeleton/infrastructure/bootstrap/src/templates/app/backend/routes.json +10 -0
  21. skeleton/infrastructure/bootstrap/src/templates/app/backend/src/__init__.py +0 -0
  22. skeleton/infrastructure/bootstrap/src/templates/app/backend/src/main.py +35 -0
  23. skeleton/infrastructure/bootstrap/src/templates/app/frontend/package.json +21 -0
  24. skeleton/infrastructure/bootstrap/src/templates/app/frontend/skeleton-config.json +29 -0
  25. skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/index.html +16 -0
  26. skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/main.tsx +15 -0
  27. skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/css/styles.css +28 -0
  28. skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/img/skeleton.png +0 -0
  29. skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/js/script.js +1 -0
  30. skeleton/infrastructure/bootstrap/src/templates/app/frontend/tsconfig.json +90 -0
  31. skeleton/infrastructure/bootstrap/src/templates/config/.gitignore +21 -0
  32. skeleton/infrastructure/bootstrap/src/templates/config/skeleton-dependencies.json +21 -0
  33. skeleton/infrastructure/bootstrap/src/templates/config/skeleton-layout.json +11 -0
  34. skeleton/infrastructure/bootstrap/src/templates/config/skeleton-schemas.json +20 -0
  35. skeleton/infrastructure/bootstrap/src/templates/config/skeleton-servers.json +16 -0
  36. skeleton/infrastructure/common/__init__.py +1 -0
  37. skeleton/infrastructure/common/config.json +8 -0
  38. skeleton/infrastructure/common/src/__init__.py +0 -0
  39. skeleton/infrastructure/common/src/config.py +37 -0
  40. skeleton/infrastructure/common/src/constants.py +4 -0
  41. skeleton/infrastructure/common/src/context.py +15 -0
  42. skeleton/infrastructure/common/src/env.py +12 -0
  43. skeleton/infrastructure/common/src/layout.py +68 -0
  44. skeleton/infrastructure/common/src/loader.py +8 -0
  45. skeleton/infrastructure/common/src/repository.py +12 -0
  46. skeleton/infrastructure/common/src/utils.py +26 -0
  47. skeleton/infrastructure/gateway/__init__.py +0 -0
  48. skeleton/infrastructure/gateway/settings/proxy.json +24 -0
  49. skeleton/infrastructure/gateway/settings/services.json +10 -0
  50. skeleton/infrastructure/gateway/src/__init__.py +0 -0
  51. skeleton/infrastructure/gateway/src/app.py +98 -0
  52. skeleton/infrastructure/gateway/src/client.py +68 -0
  53. skeleton/infrastructure/gateway/src/config.py +29 -0
  54. skeleton/infrastructure/gateway/src/context.py +31 -0
  55. skeleton/infrastructure/gateway/src/headers.py +17 -0
  56. skeleton/infrastructure/gateway/src/lifespan.py +17 -0
  57. skeleton/infrastructure/gateway/src/loader.py +113 -0
  58. skeleton/infrastructure/gateway/src/logging.py +37 -0
  59. skeleton/infrastructure/gateway/src/main.py +139 -0
  60. skeleton/infrastructure/gateway/src/parser.py +59 -0
  61. skeleton/infrastructure/gateway/src/registry.py +30 -0
  62. skeleton/infrastructure/gateway/src/reverseproxy.py +54 -0
  63. skeleton/infrastructure/gateway/src/router.py +74 -0
  64. skeleton/infrastructure/gateway/src/schemas.py +21 -0
  65. skeleton/infrastructure/gateway/src/service.py +123 -0
  66. skeleton/infrastructure/gateway/src/utils.py +48 -0
  67. skeleton/infrastructure/gateway/src/validation.py +89 -0
  68. skeleton/infrastructure/layout.json +12 -0
  69. skeleton/infrastructure/runners/__init__.py +0 -0
  70. skeleton/infrastructure/runners/src/__init__.py +0 -0
  71. skeleton/infrastructure/runners/src/actions.py +4 -0
  72. skeleton/infrastructure/runners/src/browser.py +18 -0
  73. skeleton/infrastructure/runners/src/cmd.py +7 -0
  74. skeleton/infrastructure/runners/src/command.py +7 -0
  75. skeleton/infrastructure/runners/src/frontend.py +35 -0
  76. skeleton/infrastructure/runners/src/gateway.py +104 -0
  77. skeleton/infrastructure/runners/src/hook.py +53 -0
  78. skeleton/infrastructure/runners/src/main.py +78 -0
  79. skeleton/infrastructure/runners/src/packages.py +35 -0
  80. skeleton/infrastructure/runners/src/parser.py +84 -0
  81. skeleton/infrastructure/runners/src/process.py +99 -0
  82. skeleton/infrastructure/runners/src/services.py +16 -0
  83. skeleton/infrastructure/runners/src/utils.py +6 -0
  84. skeleton/infrastructure/runners/src/validation.py +28 -0
  85. skeleton/packages/.skeleton/skeleton-cli-1.0.0.tgz +0 -0
  86. skeleton/packages/.skeleton/skeleton-webpack-1.0.0.tgz +0 -0
  87. skeleton/root.py +234 -0
  88. skeleton_framework-0.1.0.dist-info/METADATA +440 -0
  89. skeleton_framework-0.1.0.dist-info/RECORD +93 -0
  90. skeleton_framework-0.1.0.dist-info/WHEEL +5 -0
  91. skeleton_framework-0.1.0.dist-info/entry_points.txt +2 -0
  92. skeleton_framework-0.1.0.dist-info/licenses/LICENSE +146 -0
  93. skeleton_framework-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,486 @@
1
+ import json
2
+ import os
3
+ import re
4
+ import shutil
5
+
6
+ from pathlib import Path
7
+
8
+ from skeleton.infrastructure.bootstrap.src.copy import (
9
+ copy_app_template,
10
+ )
11
+
12
+ from skeleton.infrastructure.bootstrap.src.load import (
13
+ load_dependencies,
14
+ load_layout,
15
+ )
16
+
17
+ CLIENT_REQUIREMENTS_MARKER = (
18
+ "# --- Skeleton client dependencies (managed) ---"
19
+ )
20
+
21
+ def _requirement_name(requirement: str) -> str | None:
22
+
23
+ requirement = requirement.strip()
24
+
25
+ if not requirement or requirement.startswith("#"):
26
+ return None
27
+
28
+ match = re.match(
29
+ r"^([A-Za-z0-9][A-Za-z0-9._-]*)",
30
+ requirement,
31
+ )
32
+
33
+ if not match:
34
+ return None
35
+
36
+ return re.sub(
37
+ r"[-_.]+",
38
+ "-",
39
+ match.group(1),
40
+ ).lower()
41
+
42
+ def _merge_backend_requirements(
43
+ requirements: Path,
44
+ client_packages: dict[str, str],
45
+ ) -> None:
46
+
47
+ if not requirements.is_file():
48
+ raise FileNotFoundError(
49
+ f"Backend template requirements not found: {requirements}"
50
+ )
51
+
52
+ if not isinstance(client_packages, dict):
53
+ raise ValueError(
54
+ "skeleton-dependencies.json: "
55
+ "'backend.requirements' must be an object mapping "
56
+ "package names to version specifications."
57
+ )
58
+
59
+ lines = requirements.read_text(
60
+ encoding="utf-8",
61
+ ).splitlines()
62
+
63
+ # ---------------------------------------------------------
64
+ # Remove the previously generated client section
65
+ # ---------------------------------------------------------
66
+
67
+ try:
68
+ marker_index = lines.index(
69
+ CLIENT_REQUIREMENTS_MARKER
70
+ )
71
+ base_lines = lines[:marker_index]
72
+
73
+ except ValueError:
74
+ base_lines = lines
75
+
76
+ # ---------------------------------------------------------
77
+ # Identify packages already required by the template
78
+ # ---------------------------------------------------------
79
+
80
+ existing_names = {
81
+ name
82
+ for line in base_lines
83
+ if (
84
+ name := _requirement_name(line)
85
+ ) is not None
86
+ }
87
+
88
+ # ---------------------------------------------------------
89
+ # Build the managed client requirements
90
+ # ---------------------------------------------------------
91
+
92
+ client_lines: list[str] = []
93
+ client_names: set[str] = set()
94
+
95
+ package_pattern = re.compile(
96
+ r"^[A-Za-z0-9][A-Za-z0-9._-]*$"
97
+ )
98
+
99
+ for package, specifier in client_packages.items():
100
+
101
+ # Validate the package name.
102
+ if (
103
+ not isinstance(package, str)
104
+ or not package_pattern.fullmatch(package)
105
+ ):
106
+ raise ValueError(
107
+ f"Invalid Python package name: {package!r}"
108
+ )
109
+
110
+ # Validate the version specification.
111
+ if (
112
+ not isinstance(specifier, str)
113
+ or not specifier.strip()
114
+ ):
115
+ raise ValueError(
116
+ f"Invalid version specification for "
117
+ f"{package!r}: {specifier!r}. "
118
+ "Use a non-empty PEP 440 version specification, "
119
+ "such as '>=2.0,<3.0' or '==2.1.0'."
120
+ )
121
+
122
+ specifier = specifier.strip()
123
+
124
+ if not re.match(
125
+ r"^(===|~=|==|!=|<=|>=|<|>)",
126
+ specifier,
127
+ ):
128
+ raise ValueError(
129
+ f"Invalid version specification for "
130
+ f"{package!r}: {specifier!r}. "
131
+ "Expected a version constraint such as "
132
+ "'>=2.0,<3.0' or '==2.1.0'."
133
+ )
134
+
135
+ name = re.sub(
136
+ r"[-_.]+",
137
+ "-",
138
+ package,
139
+ ).lower()
140
+
141
+ # Reject duplicate names with different capitalization
142
+ # or punctuation, e.g. SQLAlchemy and sqlalchemy.
143
+ if name in client_names:
144
+ raise ValueError(
145
+ f"Duplicate backend dependency: {package!r}"
146
+ )
147
+
148
+ client_names.add(name)
149
+
150
+ # Do not silently ignore a client's version constraint
151
+ # when the template already declares the same package.
152
+ if name in existing_names:
153
+ raise ValueError(
154
+ f"Backend dependency conflict for {package!r}: "
155
+ "the package is already declared in the backend "
156
+ "template requirements. Reconcile the template "
157
+ "and client configuration before initialization."
158
+ )
159
+
160
+ client_lines.append(
161
+ f"{package}{specifier}"
162
+ )
163
+
164
+ # ---------------------------------------------------------
165
+ # Rebuild requirements.txt
166
+ # ---------------------------------------------------------
167
+
168
+ while base_lines and not base_lines[-1].strip():
169
+ base_lines.pop()
170
+
171
+ merged_lines = list(base_lines)
172
+
173
+ if client_lines:
174
+
175
+ if merged_lines:
176
+ merged_lines.append("")
177
+
178
+ merged_lines.append(
179
+ CLIENT_REQUIREMENTS_MARKER
180
+ )
181
+ merged_lines.extend(client_lines)
182
+
183
+ requirements.write_text(
184
+ "\n".join(merged_lines) + "\n",
185
+ encoding="utf-8",
186
+ )
187
+
188
+ def _merge_frontend_dependencies(
189
+ package_json: Path,
190
+ frontend_config: dict,
191
+ tarballs: dict[str, Path],
192
+ frontend_root: Path,
193
+ ) -> None:
194
+
195
+ if not package_json.is_file():
196
+ raise FileNotFoundError(
197
+ f"Frontend package manifest not found: {package_json}"
198
+ )
199
+
200
+ # ---------------------------------------------------------
201
+ # Load the frontend package manifest
202
+ # ---------------------------------------------------------
203
+
204
+ with package_json.open(
205
+ "r",
206
+ encoding="utf-8",
207
+ ) as file:
208
+ package = json.load(file)
209
+
210
+ # ---------------------------------------------------------
211
+ # Read configured dependency groups
212
+ # ---------------------------------------------------------
213
+
214
+ configured_dependencies = frontend_config.get(
215
+ "dependencies",
216
+ {},
217
+ )
218
+
219
+ configured_dev_dependencies = frontend_config.get(
220
+ "devDependencies",
221
+ {},
222
+ )
223
+
224
+ if not isinstance(configured_dependencies, dict):
225
+ raise ValueError(
226
+ "skeleton-dependencies.json: "
227
+ "'frontend.packages.dependencies' must be an object."
228
+ )
229
+
230
+ if not isinstance(configured_dev_dependencies, dict):
231
+ raise ValueError(
232
+ "skeleton-dependencies.json: "
233
+ "'frontend.packages.devDependencies' must be an object."
234
+ )
235
+
236
+ duplicates = (
237
+ configured_dependencies.keys()
238
+ & configured_dev_dependencies.keys()
239
+ )
240
+
241
+ if duplicates:
242
+ names = ", ".join(sorted(duplicates))
243
+ raise ValueError(
244
+ "Frontend packages cannot be declared in both "
245
+ f"'dependencies' and 'devDependencies': {names}"
246
+ )
247
+
248
+ # ---------------------------------------------------------
249
+ # Get the package manifest dependency sections
250
+ # ---------------------------------------------------------
251
+
252
+ dependencies = package.setdefault(
253
+ "dependencies",
254
+ {},
255
+ )
256
+
257
+ dev_dependencies = package.setdefault(
258
+ "devDependencies",
259
+ {},
260
+ )
261
+
262
+ if not isinstance(dependencies, dict):
263
+ raise ValueError(
264
+ f"'dependencies' must be an object in {package_json}"
265
+ )
266
+
267
+ if not isinstance(dev_dependencies, dict):
268
+ raise ValueError(
269
+ f"'devDependencies' must be an object in {package_json}"
270
+ )
271
+
272
+ # ---------------------------------------------------------
273
+ # Reconcile configured application dependencies
274
+ # ---------------------------------------------------------
275
+
276
+ for name in (
277
+ configured_dependencies.keys()
278
+ | configured_dev_dependencies.keys()
279
+ ):
280
+
281
+ # Remove any previous declaration from either group.
282
+ dependencies.pop(name, None)
283
+ dev_dependencies.pop(name, None)
284
+
285
+ dependencies.update(configured_dependencies)
286
+ dev_dependencies.update(configured_dev_dependencies)
287
+
288
+ # ---------------------------------------------------------
289
+ # Configure local Skeleton package tarballs
290
+ # ---------------------------------------------------------
291
+
292
+ for package_name, source_tarball in tarballs.items():
293
+
294
+ # Skeleton tooling belongs in devDependencies.
295
+ dependencies.pop(package_name, None)
296
+
297
+ relative_path = Path(
298
+ os.path.relpath(
299
+ source_tarball,
300
+ frontend_root,
301
+ )
302
+ ).as_posix()
303
+
304
+ dev_dependencies[package_name] = (
305
+ f"file:{relative_path}"
306
+ )
307
+
308
+ # ---------------------------------------------------------
309
+ # Write the reconciled frontend package manifest
310
+ # ---------------------------------------------------------
311
+
312
+ with package_json.open(
313
+ "w",
314
+ encoding="utf-8",
315
+ ) as file:
316
+ json.dump(
317
+ package,
318
+ file,
319
+ indent=4,
320
+ )
321
+ file.write("\n")
322
+
323
+ def init(
324
+ framework_root: Path,
325
+ project_root: Path,
326
+ ) -> None:
327
+
328
+ framework_root = framework_root.resolve()
329
+ project_root = project_root.resolve()
330
+
331
+ templates = (
332
+ framework_root
333
+ / "skeleton"
334
+ / "infrastructure"
335
+ / "bootstrap"
336
+ / "src"
337
+ / "templates"
338
+ / "app"
339
+ )
340
+
341
+ if not templates.is_dir():
342
+ raise FileNotFoundError(
343
+ f"Application templates not found: {templates}"
344
+ )
345
+
346
+ layout = load_layout(
347
+ project_root / "skeleton-layout.json"
348
+ )
349
+
350
+ dependencies = load_dependencies(
351
+ project_root / "skeleton-dependencies.json"
352
+ )
353
+
354
+ print(f"INFO: Initializing project: {project_root}")
355
+
356
+ # ---------------------------------------------------------
357
+ # Copy the application template
358
+ # ---------------------------------------------------------
359
+
360
+ copy_app_template(
361
+ source=templates,
362
+ target=project_root,
363
+ frontend=layout["frontend"],
364
+ backend=layout["backend"],
365
+ )
366
+
367
+ # ---------------------------------------------------------
368
+ # Merge the configured client backend dependencies
369
+ # ---------------------------------------------------------
370
+
371
+ requirements = (
372
+ project_root
373
+ / layout["backend"]
374
+ / "requirements.txt"
375
+ )
376
+
377
+ _merge_backend_requirements(
378
+ requirements,
379
+ dependencies["backend"],
380
+ )
381
+
382
+ print(
383
+ f"INFO: Generated backend requirements: {requirements}"
384
+ )
385
+
386
+ # ---------------------------------------------------------
387
+ # Locate the prepared Skeleton tooling packages
388
+ # ---------------------------------------------------------
389
+
390
+ source = (
391
+ framework_root
392
+ / "skeleton"
393
+ / "packages"
394
+ / ".skeleton"
395
+ )
396
+
397
+ if not source.is_dir():
398
+ raise FileNotFoundError(
399
+ f"Packaged Skeleton artifacts not found: {source}\n"
400
+ "Run 'python run.py --dev-pack' in the framework "
401
+ "repository before initializing the client."
402
+ )
403
+
404
+ package_prefixes = (
405
+ "skeleton-cli",
406
+ "skeleton-webpack",
407
+ )
408
+
409
+ tarballs: list[Path] = []
410
+
411
+ for prefix in package_prefixes:
412
+
413
+ matches = sorted(
414
+ source.glob(f"{prefix}-*.tgz")
415
+ )
416
+
417
+ if len(matches) != 1:
418
+ raise FileNotFoundError(
419
+ f"Expected exactly one {prefix} tarball in "
420
+ f"{source}, but found {len(matches)}."
421
+ )
422
+
423
+ tarballs.append(matches[0])
424
+
425
+ # ---------------------------------------------------------
426
+ # Copy prepared tarballs into the client project
427
+ # ---------------------------------------------------------
428
+
429
+ destination = (
430
+ project_root
431
+ / ".skeleton"
432
+ / "packages"
433
+ )
434
+
435
+ destination.mkdir(
436
+ parents=True,
437
+ exist_ok=True,
438
+ )
439
+
440
+ # Remove outdated copies of these packages only.
441
+ for prefix in package_prefixes:
442
+
443
+ for old_tarball in destination.glob(
444
+ f"{prefix}-*.tgz"
445
+ ):
446
+ old_tarball.unlink()
447
+
448
+ for tarball in tarballs:
449
+
450
+ shutil.copy2(
451
+ tarball,
452
+ destination / tarball.name,
453
+ )
454
+
455
+ print(
456
+ f"INFO: Copied Skeleton packages to {destination}"
457
+ )
458
+
459
+ # ---------------------------------------------------------
460
+ # Reconcile the frontend package manifest
461
+ # ---------------------------------------------------------
462
+
463
+ frontend_root = project_root / layout["frontend"]
464
+ package_json = frontend_root / "package.json"
465
+
466
+ local_tarballs = {
467
+ "@skeleton/cli": (
468
+ destination / tarballs[0].name
469
+ ).resolve(),
470
+ "@skeleton/webpack": (
471
+ destination / tarballs[1].name
472
+ ).resolve(),
473
+ }
474
+
475
+ _merge_frontend_dependencies(
476
+ package_json=package_json,
477
+ frontend_config=dependencies["frontend"],
478
+ tarballs=local_tarballs,
479
+ frontend_root=frontend_root,
480
+ )
481
+
482
+ print(
483
+ f"INFO: Reconciled frontend dependencies: {package_json}"
484
+ )
485
+
486
+ print("INFO: Project initialization complete")
@@ -0,0 +1,113 @@
1
+ import os, sys
2
+ import subprocess
3
+
4
+ from pathlib import Path
5
+
6
+ from skeleton.infrastructure.bootstrap.src.load import (
7
+ load_layout,
8
+ )
9
+
10
+ def install(
11
+ framework_root: Path,
12
+ project_root: Path,
13
+ ) -> None:
14
+
15
+ framework_root = framework_root.resolve()
16
+ project_root = project_root.resolve()
17
+
18
+ # ---------------------------------------------------------
19
+ # Load project configuration
20
+ # ---------------------------------------------------------
21
+
22
+ layout = load_layout(
23
+ project_root / "skeleton-layout.json"
24
+ )
25
+
26
+ # ---------------------------------------------------------
27
+ # Install backend Python dependencies
28
+ # ---------------------------------------------------------
29
+
30
+ backend_root = project_root / layout["backend"]
31
+ requirements = backend_root / "requirements.txt"
32
+ venv = backend_root / ".venv"
33
+
34
+ if not requirements.is_file():
35
+
36
+ raise FileNotFoundError(
37
+ f"Backend requirements file not found: {requirements}\n"
38
+ "Run 'python run.py --init' from the client directory first."
39
+ )
40
+
41
+ if not venv.is_dir():
42
+
43
+ print(f"INFO: Creating Python virtual environment: {venv}")
44
+
45
+ subprocess.run(
46
+ [
47
+ sys.executable,
48
+ "-m",
49
+ "venv",
50
+ str(venv),
51
+ ],
52
+ cwd=project_root,
53
+ check=True,
54
+ )
55
+
56
+ python = (
57
+ venv / "Scripts" / "python.exe"
58
+ if os.name == "nt"
59
+ else venv / "bin" / "python"
60
+ )
61
+
62
+ if not python.is_file():
63
+
64
+ raise FileNotFoundError(
65
+ f"Python executable not found in virtual environment: "
66
+ f"{python}"
67
+ )
68
+
69
+ print(f"INFO: Installing Python dependencies: {requirements}")
70
+
71
+ subprocess.run(
72
+ [
73
+ str(python),
74
+ "-m",
75
+ "pip",
76
+ "install",
77
+ "-r",
78
+ str(requirements),
79
+ ],
80
+ cwd=backend_root,
81
+ check=True,
82
+ )
83
+
84
+ # ---------------------------------------------------------
85
+ # Install frontend dependencies
86
+ # ---------------------------------------------------------
87
+
88
+ frontend_root = project_root / layout["frontend"]
89
+ package_json = frontend_root / "package.json"
90
+
91
+ if not package_json.is_file():
92
+
93
+ raise FileNotFoundError(
94
+ f"Frontend package manifest not found: {package_json}\n"
95
+ "Run 'python run.py --init' from the client directory first."
96
+ )
97
+
98
+ npm = (
99
+ "npm.cmd"
100
+ if os.name == "nt"
101
+ else "npm"
102
+ )
103
+
104
+ print(f"INFO: Installing frontend dependencies: {frontend_root}")
105
+
106
+ subprocess.run(
107
+ [npm, "install"],
108
+ cwd=frontend_root,
109
+ check=True,
110
+ )
111
+
112
+ print("INFO: Project installation complete")
113
+
@@ -0,0 +1,80 @@
1
+ import os, subprocess
2
+ from pathlib import Path
3
+
4
+ def dev_build(
5
+ framework_root: Path,
6
+ ) -> None:
7
+
8
+ framework_root = framework_root.resolve()
9
+
10
+ # ---------------------------------------------------------
11
+ # Validate framework package configuration
12
+ # ---------------------------------------------------------
13
+
14
+ pyproject = framework_root / "pyproject.toml"
15
+
16
+ if not pyproject.is_file():
17
+
18
+ raise FileNotFoundError(
19
+ f"Framework Python package configuration not found: "
20
+ f"{pyproject}"
21
+ )
22
+
23
+ # ---------------------------------------------------------
24
+ # Validate framework root development environment
25
+ # ---------------------------------------------------------
26
+
27
+ venv = framework_root / ".venv"
28
+
29
+ if not venv.is_dir():
30
+
31
+ raise FileNotFoundError(
32
+ f"Framework virtual environment not found: {venv}. "
33
+ "Run --dev-init first."
34
+ )
35
+
36
+ python = (
37
+ venv / "Scripts" / "python.exe"
38
+ if os.name == "nt"
39
+ else venv / "bin" / "python"
40
+ )
41
+
42
+ if not python.is_file():
43
+
44
+ raise FileNotFoundError(
45
+ f"Framework Python executable not found: {python}"
46
+ )
47
+
48
+ # ---------------------------------------------------------
49
+ # Validate Python build requirements
50
+ # ---------------------------------------------------------
51
+
52
+ requirements = framework_root / "requirements.txt"
53
+
54
+ if not requirements.is_file():
55
+
56
+ raise FileNotFoundError(
57
+ f"Framework Python requirements not found: "
58
+ f"{requirements}"
59
+ )
60
+
61
+ # ---------------------------------------------------------
62
+ # Build distributable Python package
63
+ # ---------------------------------------------------------
64
+
65
+ print(
66
+ f"INFO: Building Skeleton Python package: "
67
+ f"{framework_root}"
68
+ )
69
+
70
+ subprocess.run(
71
+ [
72
+ str(python),
73
+ "-m",
74
+ "build",
75
+ ],
76
+ cwd=framework_root,
77
+ check=True,
78
+ )
79
+
80
+ print("INFO: Skeleton Python package build complete")