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.
- skeleton/__init__.py +0 -0
- skeleton/cli.py +151 -0
- skeleton/client.py +269 -0
- skeleton/infrastructure/__init__.py +1 -0
- skeleton/infrastructure/bootstrap/__init__.py +0 -0
- skeleton/infrastructure/bootstrap/src/__init__.py +0 -0
- skeleton/infrastructure/bootstrap/src/commands/__init__.py +0 -0
- skeleton/infrastructure/bootstrap/src/commands/client/__init__.py +0 -0
- skeleton/infrastructure/bootstrap/src/commands/client/init.py +486 -0
- skeleton/infrastructure/bootstrap/src/commands/client/install.py +113 -0
- skeleton/infrastructure/bootstrap/src/commands/dev/__init__.py +0 -0
- skeleton/infrastructure/bootstrap/src/commands/dev/build.py +80 -0
- skeleton/infrastructure/bootstrap/src/commands/dev/init.py +80 -0
- skeleton/infrastructure/bootstrap/src/commands/dev/install.py +168 -0
- skeleton/infrastructure/bootstrap/src/commands/dev/pack.py +55 -0
- skeleton/infrastructure/bootstrap/src/config.py +54 -0
- skeleton/infrastructure/bootstrap/src/copy.py +56 -0
- skeleton/infrastructure/bootstrap/src/load.py +221 -0
- skeleton/infrastructure/bootstrap/src/templates/app/backend/requirements.txt +1 -0
- skeleton/infrastructure/bootstrap/src/templates/app/backend/routes.json +10 -0
- skeleton/infrastructure/bootstrap/src/templates/app/backend/src/__init__.py +0 -0
- skeleton/infrastructure/bootstrap/src/templates/app/backend/src/main.py +35 -0
- skeleton/infrastructure/bootstrap/src/templates/app/frontend/package.json +21 -0
- skeleton/infrastructure/bootstrap/src/templates/app/frontend/skeleton-config.json +29 -0
- skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/index.html +16 -0
- skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/main.tsx +15 -0
- skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/css/styles.css +28 -0
- skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/img/skeleton.png +0 -0
- skeleton/infrastructure/bootstrap/src/templates/app/frontend/src/static/js/script.js +1 -0
- skeleton/infrastructure/bootstrap/src/templates/app/frontend/tsconfig.json +90 -0
- skeleton/infrastructure/bootstrap/src/templates/config/.gitignore +21 -0
- skeleton/infrastructure/bootstrap/src/templates/config/skeleton-dependencies.json +21 -0
- skeleton/infrastructure/bootstrap/src/templates/config/skeleton-layout.json +11 -0
- skeleton/infrastructure/bootstrap/src/templates/config/skeleton-schemas.json +20 -0
- skeleton/infrastructure/bootstrap/src/templates/config/skeleton-servers.json +16 -0
- skeleton/infrastructure/common/__init__.py +1 -0
- skeleton/infrastructure/common/config.json +8 -0
- skeleton/infrastructure/common/src/__init__.py +0 -0
- skeleton/infrastructure/common/src/config.py +37 -0
- skeleton/infrastructure/common/src/constants.py +4 -0
- skeleton/infrastructure/common/src/context.py +15 -0
- skeleton/infrastructure/common/src/env.py +12 -0
- skeleton/infrastructure/common/src/layout.py +68 -0
- skeleton/infrastructure/common/src/loader.py +8 -0
- skeleton/infrastructure/common/src/repository.py +12 -0
- skeleton/infrastructure/common/src/utils.py +26 -0
- skeleton/infrastructure/gateway/__init__.py +0 -0
- skeleton/infrastructure/gateway/settings/proxy.json +24 -0
- skeleton/infrastructure/gateway/settings/services.json +10 -0
- skeleton/infrastructure/gateway/src/__init__.py +0 -0
- skeleton/infrastructure/gateway/src/app.py +98 -0
- skeleton/infrastructure/gateway/src/client.py +68 -0
- skeleton/infrastructure/gateway/src/config.py +29 -0
- skeleton/infrastructure/gateway/src/context.py +31 -0
- skeleton/infrastructure/gateway/src/headers.py +17 -0
- skeleton/infrastructure/gateway/src/lifespan.py +17 -0
- skeleton/infrastructure/gateway/src/loader.py +113 -0
- skeleton/infrastructure/gateway/src/logging.py +37 -0
- skeleton/infrastructure/gateway/src/main.py +139 -0
- skeleton/infrastructure/gateway/src/parser.py +59 -0
- skeleton/infrastructure/gateway/src/registry.py +30 -0
- skeleton/infrastructure/gateway/src/reverseproxy.py +54 -0
- skeleton/infrastructure/gateway/src/router.py +74 -0
- skeleton/infrastructure/gateway/src/schemas.py +21 -0
- skeleton/infrastructure/gateway/src/service.py +123 -0
- skeleton/infrastructure/gateway/src/utils.py +48 -0
- skeleton/infrastructure/gateway/src/validation.py +89 -0
- skeleton/infrastructure/layout.json +12 -0
- skeleton/infrastructure/runners/__init__.py +0 -0
- skeleton/infrastructure/runners/src/__init__.py +0 -0
- skeleton/infrastructure/runners/src/actions.py +4 -0
- skeleton/infrastructure/runners/src/browser.py +18 -0
- skeleton/infrastructure/runners/src/cmd.py +7 -0
- skeleton/infrastructure/runners/src/command.py +7 -0
- skeleton/infrastructure/runners/src/frontend.py +35 -0
- skeleton/infrastructure/runners/src/gateway.py +104 -0
- skeleton/infrastructure/runners/src/hook.py +53 -0
- skeleton/infrastructure/runners/src/main.py +78 -0
- skeleton/infrastructure/runners/src/packages.py +35 -0
- skeleton/infrastructure/runners/src/parser.py +84 -0
- skeleton/infrastructure/runners/src/process.py +99 -0
- skeleton/infrastructure/runners/src/services.py +16 -0
- skeleton/infrastructure/runners/src/utils.py +6 -0
- skeleton/infrastructure/runners/src/validation.py +28 -0
- skeleton/packages/.skeleton/skeleton-cli-1.0.0.tgz +0 -0
- skeleton/packages/.skeleton/skeleton-webpack-1.0.0.tgz +0 -0
- skeleton/root.py +234 -0
- skeleton_framework-0.1.0.dist-info/METADATA +440 -0
- skeleton_framework-0.1.0.dist-info/RECORD +93 -0
- skeleton_framework-0.1.0.dist-info/WHEEL +5 -0
- skeleton_framework-0.1.0.dist-info/entry_points.txt +2 -0
- skeleton_framework-0.1.0.dist-info/licenses/LICENSE +146 -0
- 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
|
+
|
|
File without changes
|
|
@@ -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")
|