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,123 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from asyncio.subprocess import Process
|
|
5
|
+
|
|
6
|
+
from skeleton.infrastructure.gateway.src.context import ServiceContext
|
|
7
|
+
|
|
8
|
+
# actual runtime services!
|
|
9
|
+
class Service:
|
|
10
|
+
|
|
11
|
+
def __init__(self, config: ServiceContext):
|
|
12
|
+
|
|
13
|
+
self.config = config
|
|
14
|
+
self.proc: Process | None = None
|
|
15
|
+
|
|
16
|
+
self.watch_task: asyncio.Task | None = None
|
|
17
|
+
|
|
18
|
+
# avoid race conditions & multiple restarts
|
|
19
|
+
self.restart_lock = asyncio.Lock()
|
|
20
|
+
|
|
21
|
+
async def run(self) -> None:
|
|
22
|
+
await self.start()
|
|
23
|
+
self.watch_task = asyncio.create_task(self.watch_loop())
|
|
24
|
+
|
|
25
|
+
async def start(self) -> None:
|
|
26
|
+
|
|
27
|
+
print(
|
|
28
|
+
f"INFO: Starting {self.config.name}"
|
|
29
|
+
f" at http://{self.config.network.host}:"
|
|
30
|
+
f"{self.config.network.port}"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self.proc = await asyncio.create_subprocess_exec(
|
|
34
|
+
self.config.process.exe, "-m", "uvicorn",
|
|
35
|
+
f"{self.config.process.module}:{self.config.name}",
|
|
36
|
+
"--host", f"{self.config.network.host}",
|
|
37
|
+
"--port", f"{self.config.network.port}",
|
|
38
|
+
cwd=self.config.process.cwd,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# This loop runs forever ...
|
|
42
|
+
async def watch_loop(self) -> None:
|
|
43
|
+
|
|
44
|
+
print(
|
|
45
|
+
f"INFO {self.config.name}"
|
|
46
|
+
f" hot-module reload {self.config.watch.enabled}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if not self.config.watch.enabled:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# lazy import awatch!
|
|
53
|
+
from watchfiles import awatch # type: ignore
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
async for changes in awatch(self.config.watch.directory):
|
|
57
|
+
# check whether changes contains a file having extension
|
|
58
|
+
# that corresponds to any of the extensions in the config list!
|
|
59
|
+
if any(
|
|
60
|
+
Path(path).suffix
|
|
61
|
+
in self.config.watch.extensions
|
|
62
|
+
for _, path in changes
|
|
63
|
+
):
|
|
64
|
+
await asyncio.sleep(0.25)
|
|
65
|
+
await self.restart()
|
|
66
|
+
|
|
67
|
+
except asyncio.CancelledError:
|
|
68
|
+
raise
|
|
69
|
+
|
|
70
|
+
# proper cleanup needed!
|
|
71
|
+
async def stop(self) -> None:
|
|
72
|
+
# check: does subprocess exist? + still running? (return code None)
|
|
73
|
+
if self.proc is not None and self.proc.returncode is None:
|
|
74
|
+
print(
|
|
75
|
+
f"INFO: Terminating "
|
|
76
|
+
f"{self.config.name} at "
|
|
77
|
+
f"{self.config.network.host} on "
|
|
78
|
+
f"{self.config.network.port}"
|
|
79
|
+
)
|
|
80
|
+
self.proc.terminate()
|
|
81
|
+
await self.proc.wait()
|
|
82
|
+
|
|
83
|
+
# context manager in restart() is equivalent but safer:
|
|
84
|
+
#
|
|
85
|
+
# await self.restart_lock.acquire()
|
|
86
|
+
#
|
|
87
|
+
# try:
|
|
88
|
+
# await self.stop()
|
|
89
|
+
# await self.start()
|
|
90
|
+
#
|
|
91
|
+
# finally:
|
|
92
|
+
# self.restart_lock.release()
|
|
93
|
+
#
|
|
94
|
+
# # async with context manager is safer
|
|
95
|
+
async def restart(self) -> None:
|
|
96
|
+
# async mutex lock => prevent multiple restarts!
|
|
97
|
+
# ONLY ONE coroutine may enter this block at any instance!
|
|
98
|
+
async with self.restart_lock:
|
|
99
|
+
print(
|
|
100
|
+
f"INFO: Restarting "
|
|
101
|
+
f"{self.config.name} at "
|
|
102
|
+
f"{self.config.network.host} on "
|
|
103
|
+
f"{self.config.network.port}"
|
|
104
|
+
)
|
|
105
|
+
await self.stop()
|
|
106
|
+
await self.start()
|
|
107
|
+
|
|
108
|
+
async def shutdown(self):
|
|
109
|
+
|
|
110
|
+
# cancel watcher tasks FIRST!
|
|
111
|
+
if self.watch_task:
|
|
112
|
+
|
|
113
|
+
self.watch_task.cancel()
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
await self.watch_task
|
|
117
|
+
except asyncio.CancelledError:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# THEN terminate subprocess!
|
|
121
|
+
await self.stop()
|
|
122
|
+
print("INFO: Graceful shutdown complete!")
|
|
123
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from skeleton.infrastructure.common.src.context import RuntimeContext
|
|
4
|
+
|
|
5
|
+
def build_static_dir(context: RuntimeContext) -> Path:
|
|
6
|
+
|
|
7
|
+
frontend = context.layout.frontend
|
|
8
|
+
lib = context.config.frontend["lib"]
|
|
9
|
+
|
|
10
|
+
build_dir = (
|
|
11
|
+
lib["src"]
|
|
12
|
+
if context.mode == "dev"
|
|
13
|
+
else lib["dist"]
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
return frontend/build_dir/lib["static"]
|
|
17
|
+
|
|
18
|
+
def normalize_extensions(
|
|
19
|
+
ext: tuple[str, ...]
|
|
20
|
+
) -> tuple[str, ...]:
|
|
21
|
+
|
|
22
|
+
# store all normalized extensions!
|
|
23
|
+
normalized_ext: list[str] = []
|
|
24
|
+
|
|
25
|
+
for item in ext:
|
|
26
|
+
|
|
27
|
+
if not isinstance(item, str):
|
|
28
|
+
raise TypeError(f"Extension {item} must be a string")
|
|
29
|
+
|
|
30
|
+
# remove any leading/trailing whitespace
|
|
31
|
+
item = item.strip()
|
|
32
|
+
|
|
33
|
+
# check that extension is not a path
|
|
34
|
+
if "/" in item or "\\" in item:
|
|
35
|
+
raise ValueError(f"Invalid extension {item}")
|
|
36
|
+
|
|
37
|
+
# check that extension is a proper file type
|
|
38
|
+
if item == ".":
|
|
39
|
+
raise ValueError(f"'.' is not a valid extension/file type")
|
|
40
|
+
|
|
41
|
+
# if the item does NOT start with . then pre-pend it!
|
|
42
|
+
if not item.startswith("."):
|
|
43
|
+
item = f".{item}"
|
|
44
|
+
|
|
45
|
+
# if all is OK!
|
|
46
|
+
normalized_ext.append(item)
|
|
47
|
+
|
|
48
|
+
return tuple(normalized_ext)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from skeleton.infrastructure.gateway.src.config import ServiceConfig
|
|
4
|
+
from skeleton.infrastructure.gateway.src.context import ServiceContext
|
|
5
|
+
|
|
6
|
+
from skeleton.infrastructure.common.src.utils import (
|
|
7
|
+
check_file_exists,
|
|
8
|
+
module_to_path,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
class ConfigValidationError(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def validate_service_configs(
|
|
15
|
+
services: list[ServiceConfig],
|
|
16
|
+
) -> bool:
|
|
17
|
+
|
|
18
|
+
used_ports: set[int] = set()
|
|
19
|
+
|
|
20
|
+
for service in services:
|
|
21
|
+
validate_service_root(service.root)
|
|
22
|
+
|
|
23
|
+
validate_service_module(
|
|
24
|
+
service.process.module,
|
|
25
|
+
service.root,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
validate_service_port(
|
|
29
|
+
service.network.port,
|
|
30
|
+
used_ports,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_service_contexts(
|
|
37
|
+
services: list[ServiceContext],
|
|
38
|
+
) -> bool:
|
|
39
|
+
|
|
40
|
+
for service in services:
|
|
41
|
+
validate_service_executable(service.process.exe)
|
|
42
|
+
validate_service_watch_dir(service.watch.directory)
|
|
43
|
+
validate_service_cwd(service.process.cwd)
|
|
44
|
+
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
def validate_service_root(root: Path) -> None:
|
|
48
|
+
if not root.exists():
|
|
49
|
+
raise FileNotFoundError(
|
|
50
|
+
f"service root does not exist: {root}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def validate_service_module(
|
|
55
|
+
module: str,
|
|
56
|
+
root: Path,
|
|
57
|
+
) -> None:
|
|
58
|
+
|
|
59
|
+
path = root / module_to_path(module)
|
|
60
|
+
|
|
61
|
+
check_file_exists("module", path)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def validate_service_port(
|
|
65
|
+
port: int,
|
|
66
|
+
used_ports: set[int],
|
|
67
|
+
) -> None:
|
|
68
|
+
|
|
69
|
+
if port in used_ports:
|
|
70
|
+
raise ConfigValidationError(
|
|
71
|
+
f"Duplicate port detected: {port}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
used_ports.add(port)
|
|
75
|
+
|
|
76
|
+
def validate_service_executable(exe: Path) -> None:
|
|
77
|
+
check_file_exists("exe", exe)
|
|
78
|
+
|
|
79
|
+
def validate_service_cwd(cwd: Path) -> None:
|
|
80
|
+
if not cwd.exists():
|
|
81
|
+
raise FileNotFoundError(
|
|
82
|
+
f"cwd does not exist: {cwd}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def validate_service_watch_dir(watch_dir: Path) -> None:
|
|
86
|
+
if not watch_dir.exists():
|
|
87
|
+
raise FileNotFoundError(
|
|
88
|
+
f"watch directory does not exist: {watch_dir}"
|
|
89
|
+
)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import webbrowser
|
|
2
|
+
|
|
3
|
+
class BrowserOpener():
|
|
4
|
+
def open(self, url: str):
|
|
5
|
+
raise NotImplementedError(
|
|
6
|
+
f"No browser opener implemented!"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
class SystemBrowserOpener(BrowserOpener):
|
|
10
|
+
def open(self, url: str) -> None:
|
|
11
|
+
webbrowser.open(url)
|
|
12
|
+
|
|
13
|
+
def resolve_browser(
|
|
14
|
+
choice: str
|
|
15
|
+
) -> BrowserOpener | None:
|
|
16
|
+
if choice == "system":
|
|
17
|
+
return SystemBrowserOpener()
|
|
18
|
+
return None
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from skeleton.infrastructure.runners.src.command import Command
|
|
2
|
+
from skeleton.infrastructure.common.src.context import RuntimeContext
|
|
3
|
+
from skeleton.infrastructure.runners.src.utils import resolve_cmd
|
|
4
|
+
|
|
5
|
+
def start(
|
|
6
|
+
context: RuntimeContext,
|
|
7
|
+
flags: list[str] | None = None
|
|
8
|
+
) -> Command:
|
|
9
|
+
|
|
10
|
+
flags = flags or []
|
|
11
|
+
|
|
12
|
+
return Command(
|
|
13
|
+
args=[
|
|
14
|
+
resolve_cmd("npm"),
|
|
15
|
+
"run", "serve",
|
|
16
|
+
*flags
|
|
17
|
+
],
|
|
18
|
+
cwd=context.root / context.layout.frontend
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def build(
|
|
22
|
+
context: RuntimeContext,
|
|
23
|
+
flags: list[str] | None = None
|
|
24
|
+
) -> Command:
|
|
25
|
+
|
|
26
|
+
flags = flags or []
|
|
27
|
+
|
|
28
|
+
return Command(
|
|
29
|
+
args=[
|
|
30
|
+
resolve_cmd("npm"),
|
|
31
|
+
"run", "build",
|
|
32
|
+
*flags
|
|
33
|
+
],
|
|
34
|
+
cwd=context.root / context.layout.frontend
|
|
35
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from skeleton.infrastructure.common.src.constants import Bootstrap
|
|
2
|
+
from skeleton.infrastructure.common.src.context import RuntimeContext
|
|
3
|
+
|
|
4
|
+
from skeleton.infrastructure.runners.src.utils import resolve_cmd
|
|
5
|
+
from skeleton.infrastructure.runners.src.command import Command
|
|
6
|
+
|
|
7
|
+
from skeleton.infrastructure.common.src.utils import (
|
|
8
|
+
resolve_executable_path
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# def start(
|
|
12
|
+
# context: RuntimeContext,
|
|
13
|
+
# flags: list[str] | None = None
|
|
14
|
+
# ) -> Command:
|
|
15
|
+
|
|
16
|
+
# flags = flags or []
|
|
17
|
+
|
|
18
|
+
# module = Bootstrap.GATEWAY_MODULE
|
|
19
|
+
|
|
20
|
+
# framework_root = context.layout.infrastructure.parent.parent
|
|
21
|
+
|
|
22
|
+
# python_exe = resolve_executable_path(
|
|
23
|
+
# context.layout.gateway
|
|
24
|
+
# )
|
|
25
|
+
|
|
26
|
+
# args = [
|
|
27
|
+
# str(python_exe),
|
|
28
|
+
# "-m", module,
|
|
29
|
+
# "--root", str(context.root),
|
|
30
|
+
# "--mode", str(context.mode),
|
|
31
|
+
# ]
|
|
32
|
+
|
|
33
|
+
# if context.options["watch"]:
|
|
34
|
+
# args.append("--watch")
|
|
35
|
+
|
|
36
|
+
# if context.options["open"]:
|
|
37
|
+
# args.append("--open")
|
|
38
|
+
|
|
39
|
+
# return Command(
|
|
40
|
+
# args=[*args, *flags],
|
|
41
|
+
# cwd=framework_root,
|
|
42
|
+
# )
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
import sys
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
|
|
48
|
+
from skeleton.infrastructure.common.src.constants import Bootstrap
|
|
49
|
+
from skeleton.infrastructure.common.src.context import RuntimeContext
|
|
50
|
+
from skeleton.infrastructure.runners.src.utils import resolve_cmd
|
|
51
|
+
from skeleton.infrastructure.runners.src.command import Command
|
|
52
|
+
from skeleton.infrastructure.common.src.utils import resolve_executable_path
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def start(
|
|
56
|
+
context: RuntimeContext,
|
|
57
|
+
flags: list[str] | None = None,
|
|
58
|
+
) -> Command:
|
|
59
|
+
|
|
60
|
+
flags = flags or []
|
|
61
|
+
|
|
62
|
+
module = Bootstrap.GATEWAY_MODULE
|
|
63
|
+
framework_root = context.layout.infrastructure.parent.parent
|
|
64
|
+
|
|
65
|
+
# Source-repository development uses its dedicated gateway environment.
|
|
66
|
+
# An installed framework uses the Python environment containing the package.
|
|
67
|
+
if (framework_root / "run.py").is_file():
|
|
68
|
+
python_exe = resolve_executable_path(context.layout.gateway)
|
|
69
|
+
else:
|
|
70
|
+
python_exe = Path(sys.executable)
|
|
71
|
+
|
|
72
|
+
args = [
|
|
73
|
+
str(python_exe),
|
|
74
|
+
"-m", module,
|
|
75
|
+
"--root", str(context.root),
|
|
76
|
+
"--mode", str(context.mode),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
if context.options["watch"]:
|
|
80
|
+
args.append("--watch")
|
|
81
|
+
|
|
82
|
+
if context.options["open"]:
|
|
83
|
+
args.append("--open")
|
|
84
|
+
|
|
85
|
+
return Command(
|
|
86
|
+
args=[*args, *flags],
|
|
87
|
+
cwd=framework_root,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def build(
|
|
91
|
+
context: RuntimeContext,
|
|
92
|
+
flags: list[str] | None = None
|
|
93
|
+
) -> Command:
|
|
94
|
+
|
|
95
|
+
flags = flags or []
|
|
96
|
+
|
|
97
|
+
return Command(
|
|
98
|
+
args=[
|
|
99
|
+
resolve_cmd("npm"),
|
|
100
|
+
"echo \"Error: no build specified\" && exit 1",
|
|
101
|
+
*flags
|
|
102
|
+
],
|
|
103
|
+
cwd=context.root
|
|
104
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import time, threading
|
|
2
|
+
|
|
3
|
+
from skeleton.infrastructure.common.src.context import RuntimeContext
|
|
4
|
+
from skeleton.infrastructure.runners.src.browser import resolve_browser
|
|
5
|
+
|
|
6
|
+
def create_dev_portal(context: RuntimeContext):
|
|
7
|
+
|
|
8
|
+
delay = 2.0
|
|
9
|
+
servers = context.config.servers["servers"]
|
|
10
|
+
|
|
11
|
+
gateway = servers["gateway"]
|
|
12
|
+
backend = servers["backend"]
|
|
13
|
+
|
|
14
|
+
gateway_host = gateway["host"]
|
|
15
|
+
gateway_port = gateway["port"]
|
|
16
|
+
gateway_url = f"http://{gateway_host}:{gateway_port}"
|
|
17
|
+
|
|
18
|
+
# for opening backend documentation
|
|
19
|
+
# valid options include "docs"/"redoc"
|
|
20
|
+
docs = context.config.services["services"]["backend"]["docs"]
|
|
21
|
+
|
|
22
|
+
backend_host = backend["host"]
|
|
23
|
+
backend_port = backend["port"]
|
|
24
|
+
backend_url = f"http://{backend_host}:{backend_port}/{docs}"
|
|
25
|
+
|
|
26
|
+
opener = resolve_browser("system")
|
|
27
|
+
|
|
28
|
+
urls = [
|
|
29
|
+
gateway_url,
|
|
30
|
+
backend_url,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# self-object as first arg
|
|
34
|
+
def hook(manager):
|
|
35
|
+
|
|
36
|
+
if opener is None:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
def _open():
|
|
40
|
+
time.sleep(delay)
|
|
41
|
+
|
|
42
|
+
for url in urls:
|
|
43
|
+
opener.open(url)
|
|
44
|
+
|
|
45
|
+
# When ctrl + C for shutdown:
|
|
46
|
+
# Exception ignored on threading shutdown:
|
|
47
|
+
# Traceback (most recent call last):
|
|
48
|
+
# File "C:\Users\Lambda\AppData\Local\Programs\Python\Python313\Lib\threading.py", line 1540, in _shutdown
|
|
49
|
+
# _thread_shutdown()
|
|
50
|
+
threading.Thread(target=_open, daemon=True).start()
|
|
51
|
+
|
|
52
|
+
return hook
|
|
53
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from skeleton.infrastructure.runners.src.cmd import CMD
|
|
4
|
+
from skeleton.infrastructure.runners.src.process import ProcessManager
|
|
5
|
+
|
|
6
|
+
from skeleton.infrastructure.common.src.constants import Bootstrap
|
|
7
|
+
from skeleton.infrastructure.common.src.context import RuntimeContext
|
|
8
|
+
|
|
9
|
+
from skeleton.infrastructure.common.src.config import load_runtime_config
|
|
10
|
+
from skeleton.infrastructure.common.src.layout import build_layout
|
|
11
|
+
from skeleton.infrastructure.common.src.env import build_env
|
|
12
|
+
|
|
13
|
+
from skeleton.infrastructure.runners.src.parser import parse
|
|
14
|
+
|
|
15
|
+
from skeleton.infrastructure.runners.src.services import SERVICES
|
|
16
|
+
from skeleton.infrastructure.runners.src.services import DEFAULT_TARGETS
|
|
17
|
+
|
|
18
|
+
def run(
|
|
19
|
+
framework_root: Path,
|
|
20
|
+
project_root: Path,
|
|
21
|
+
argv: list[str],
|
|
22
|
+
):
|
|
23
|
+
|
|
24
|
+
raw = parse(argv)
|
|
25
|
+
|
|
26
|
+
raw["options"]["root"] = str(project_root)
|
|
27
|
+
|
|
28
|
+
if not raw["targets"]:
|
|
29
|
+
raw["targets"] = list(DEFAULT_TARGETS.copy())
|
|
30
|
+
|
|
31
|
+
cmd = CMD(**raw)
|
|
32
|
+
|
|
33
|
+
MODE = str(cmd.options.get("mode", "dev"))
|
|
34
|
+
|
|
35
|
+
env = build_env(project_root, MODE)
|
|
36
|
+
|
|
37
|
+
layout = build_layout(
|
|
38
|
+
project_root / Bootstrap.LAYOUT,
|
|
39
|
+
project_root=project_root,
|
|
40
|
+
framework_root=framework_root,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
config = load_runtime_config(
|
|
44
|
+
layout.common / Bootstrap.CONFIG_FILE_EXT,
|
|
45
|
+
project_root,
|
|
46
|
+
framework_root,
|
|
47
|
+
layout,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
context = RuntimeContext(
|
|
51
|
+
root=project_root,
|
|
52
|
+
mode=MODE,
|
|
53
|
+
args=argv,
|
|
54
|
+
env=env,
|
|
55
|
+
layout=layout,
|
|
56
|
+
config=config,
|
|
57
|
+
options=cmd.options,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
manager = ProcessManager(context)
|
|
61
|
+
|
|
62
|
+
if context.options.get("open") and context.mode == "dev":
|
|
63
|
+
|
|
64
|
+
from skeleton.infrastructure.runners.src.hook import (
|
|
65
|
+
create_dev_portal
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
manager.register_hook(
|
|
69
|
+
create_dev_portal(context),
|
|
70
|
+
"after_start"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
for target in cmd.targets:
|
|
74
|
+
service = SERVICES[target]
|
|
75
|
+
handler = getattr(service, cmd.action)
|
|
76
|
+
manager.register(handler(context))
|
|
77
|
+
|
|
78
|
+
manager.start()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from skeleton.infrastructure.runners.src.command import Command
|
|
2
|
+
from skeleton.infrastructure.common.src.context import RuntimeContext
|
|
3
|
+
from skeleton.infrastructure.runners.src.utils import resolve_cmd
|
|
4
|
+
|
|
5
|
+
def start(
|
|
6
|
+
context: RuntimeContext,
|
|
7
|
+
flags: list[str] | None = None
|
|
8
|
+
) -> Command:
|
|
9
|
+
|
|
10
|
+
flags = flags or []
|
|
11
|
+
|
|
12
|
+
return Command(
|
|
13
|
+
args=[
|
|
14
|
+
resolve_cmd("npm"),
|
|
15
|
+
"run", "packages:watch",
|
|
16
|
+
*flags
|
|
17
|
+
],
|
|
18
|
+
cwd=context.root
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def build(
|
|
22
|
+
context: RuntimeContext,
|
|
23
|
+
flags: list[str] | None = None
|
|
24
|
+
) -> Command:
|
|
25
|
+
|
|
26
|
+
flags = flags or []
|
|
27
|
+
|
|
28
|
+
return Command(
|
|
29
|
+
args=[
|
|
30
|
+
resolve_cmd("npm"),
|
|
31
|
+
"run", "packages:build",
|
|
32
|
+
*flags
|
|
33
|
+
],
|
|
34
|
+
cwd=context.root
|
|
35
|
+
)
|