pulse-framework 0.1.38a1__tar.gz → 0.1.38a2__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.
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/PKG-INFO +1 -1
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/pyproject.toml +1 -1
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/app.py +29 -6
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/cmd.py +77 -7
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/dependencies.py +87 -4
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cookies.py +5 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/env.py +1 -2
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/README.md +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/__init__.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/channel.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/__init__.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/folder_lock.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/helpers.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/models.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/packages.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/processes.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/secrets.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/cli/uvicorn_log_config.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/__init__.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/codegen.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/imports.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/js.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/templates/__init__.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/templates/layout.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/templates/route.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/templates/routes_ts.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/utils.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/components/__init__.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/components/for_.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/components/if_.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/components/react_router.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/context.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/css.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/decorators.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/form.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/helpers.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/hooks/__init__.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/hooks/core.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/hooks/effects.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/hooks/runtime.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/hooks/setup.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/hooks/stable.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/hooks/states.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/html/__init__.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/html/elements.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/html/events.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/html/props.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/html/svg.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/html/tags.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/html/tags.pyi +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/messages.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/middleware.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/plugin.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/proxy.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/py.typed +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/query.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/react_component.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/reactive.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/reactive_extensions.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/render_session.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/renderer.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/request.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/routing.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/serializer.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/state.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/types/__init__.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/types/event_handler.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/user_session.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/vdom.py +0 -0
- {pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/version.py +0 -0
|
@@ -165,7 +165,6 @@ class App:
|
|
|
165
165
|
internal_server_address: str | None = None,
|
|
166
166
|
not_found: str = "/not-found",
|
|
167
167
|
# Deployment and integration options
|
|
168
|
-
env: PulseEnv | None = None,
|
|
169
168
|
mode: PulseMode = "single-server",
|
|
170
169
|
api_prefix: str = "/_pulse",
|
|
171
170
|
cors: CORSOptions | None = None,
|
|
@@ -179,7 +178,7 @@ class App:
|
|
|
179
178
|
codegen: Optional codegen configuration.
|
|
180
179
|
"""
|
|
181
180
|
# Resolve mode from environment and expose on the app instance
|
|
182
|
-
self.env =
|
|
181
|
+
self.env = envvars.pulse_env
|
|
183
182
|
self.mode = mode
|
|
184
183
|
self.status = AppStatus.created
|
|
185
184
|
# Persist the server address for use by sessions (API calls, etc.)
|
|
@@ -326,9 +325,9 @@ class App:
|
|
|
326
325
|
)
|
|
327
326
|
# Production: use Node to serve the built bundle (Bun lacks renderToPipeableStream)
|
|
328
327
|
cmd = [
|
|
329
|
-
"
|
|
330
|
-
|
|
331
|
-
"
|
|
328
|
+
"bun",
|
|
329
|
+
"run",
|
|
330
|
+
"start",
|
|
332
331
|
"--port",
|
|
333
332
|
str(port),
|
|
334
333
|
]
|
|
@@ -464,11 +463,35 @@ class App:
|
|
|
464
463
|
self.fastapi.add_middleware(CORSMiddleware, **self.cors)
|
|
465
464
|
else:
|
|
466
465
|
# Use deployment-specific CORS settings
|
|
466
|
+
cors_config = cors_options(self.mode, self.server_address)
|
|
467
|
+
print(f"CORS config: {cors_config}")
|
|
467
468
|
self.fastapi.add_middleware(
|
|
468
469
|
CORSMiddleware,
|
|
469
|
-
**
|
|
470
|
+
**cors_config,
|
|
470
471
|
)
|
|
471
472
|
|
|
473
|
+
# Debug middleware to log CORS-related request details
|
|
474
|
+
@self.fastapi.middleware("http")
|
|
475
|
+
async def cors_debug_middleware( # pyright: ignore[reportUnusedFunction]
|
|
476
|
+
request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
477
|
+
):
|
|
478
|
+
origin = request.headers.get("origin")
|
|
479
|
+
method = request.method
|
|
480
|
+
path = request.url.path
|
|
481
|
+
print(
|
|
482
|
+
f"[CORS Debug] {method} {path} | Origin: {origin} | "
|
|
483
|
+
+ f"Mode: {self.mode} | Server: {self.server_address}"
|
|
484
|
+
)
|
|
485
|
+
response = await call_next(request)
|
|
486
|
+
allow_origin = response.headers.get("access-control-allow-origin")
|
|
487
|
+
if allow_origin:
|
|
488
|
+
print(f"[CORS Debug] Response allows origin: {allow_origin}")
|
|
489
|
+
elif origin:
|
|
490
|
+
logger.warning(
|
|
491
|
+
f"[CORS Debug] Origin {origin} present but no Access-Control-Allow-Origin header set"
|
|
492
|
+
)
|
|
493
|
+
return response
|
|
494
|
+
|
|
472
495
|
# Mount PulseContext for all FastAPI routes (no route info). Other API
|
|
473
496
|
# routes / middleware should be added at the module-level, which means
|
|
474
497
|
# this middleware will wrap all of them.
|
|
@@ -21,6 +21,7 @@ from pulse.cli.dependencies import (
|
|
|
21
21
|
DependencyError,
|
|
22
22
|
DependencyPlan,
|
|
23
23
|
DependencyResolutionError,
|
|
24
|
+
check_web_dependencies,
|
|
24
25
|
prepare_web_dependencies,
|
|
25
26
|
)
|
|
26
27
|
from pulse.cli.folder_lock import FolderLock
|
|
@@ -68,7 +69,7 @@ def run(
|
|
|
68
69
|
prod: bool = typer.Option(False, "--prod", help="Run in production env"),
|
|
69
70
|
server_only: bool = typer.Option(False, "--server-only", "--backend-only"),
|
|
70
71
|
web_only: bool = typer.Option(False, "--web-only"),
|
|
71
|
-
reload: bool = typer.Option(
|
|
72
|
+
reload: bool | None = typer.Option(None, "--reload/--no-reload"),
|
|
72
73
|
find_port: bool = typer.Option(True, "--find-port/--no-find-port"),
|
|
73
74
|
):
|
|
74
75
|
"""Run the Pulse server and web development server together."""
|
|
@@ -88,6 +89,10 @@ def run(
|
|
|
88
89
|
if len(env_flags) == 1:
|
|
89
90
|
env.pulse_env = cast(PulseEnv, env_flags[0])
|
|
90
91
|
|
|
92
|
+
# Turn on reload in dev only
|
|
93
|
+
if reload is None:
|
|
94
|
+
reload = env.pulse_env == "dev"
|
|
95
|
+
|
|
91
96
|
if server_only and web_only:
|
|
92
97
|
typer.echo("❌ Cannot use --server-only and --web-only at the same time.")
|
|
93
98
|
raise typer.Exit(1)
|
|
@@ -121,10 +126,10 @@ def run(
|
|
|
121
126
|
)
|
|
122
127
|
|
|
123
128
|
if not server_only:
|
|
124
|
-
# Skip dependency installation in production mode (assumes pre-built)
|
|
125
|
-
if
|
|
129
|
+
# Skip dependency checking and installation in production/CI mode (assumes pre-built)
|
|
130
|
+
if env.pulse_env == "dev":
|
|
126
131
|
try:
|
|
127
|
-
|
|
132
|
+
to_add = check_web_dependencies(
|
|
128
133
|
web_root,
|
|
129
134
|
pulse_version=PULSE_PY_VERSION,
|
|
130
135
|
)
|
|
@@ -135,9 +140,14 @@ def run(
|
|
|
135
140
|
console.log(f"❌ {exc}")
|
|
136
141
|
raise typer.Exit(1) from None
|
|
137
142
|
|
|
138
|
-
if
|
|
143
|
+
if to_add:
|
|
139
144
|
try:
|
|
140
|
-
|
|
145
|
+
dep_plan = prepare_web_dependencies(
|
|
146
|
+
web_root,
|
|
147
|
+
pulse_version=PULSE_PY_VERSION,
|
|
148
|
+
)
|
|
149
|
+
if dep_plan:
|
|
150
|
+
_run_dependency_plan(console, web_root, dep_plan)
|
|
141
151
|
except subprocess.CalledProcessError:
|
|
142
152
|
console.log("❌ Failed to install web dependencies with Bun.")
|
|
143
153
|
raise typer.Exit(1) from None
|
|
@@ -219,6 +229,66 @@ def generate(
|
|
|
219
229
|
console.log("⚠️ No routes found to generate")
|
|
220
230
|
|
|
221
231
|
|
|
232
|
+
@cli.command("check")
|
|
233
|
+
def check(
|
|
234
|
+
app_file: str = typer.Argument(
|
|
235
|
+
..., help="App target: 'path.py[:var]' (default :app) or 'module:var'"
|
|
236
|
+
),
|
|
237
|
+
fix: bool = typer.Option(
|
|
238
|
+
False, "--fix", help="Install missing or outdated dependencies"
|
|
239
|
+
),
|
|
240
|
+
):
|
|
241
|
+
"""Check if web project dependencies are in sync with Pulse app requirements."""
|
|
242
|
+
console = Console()
|
|
243
|
+
|
|
244
|
+
console.log(f"📁 Loading app from: {app_file}")
|
|
245
|
+
app_ctx = load_app_from_target(app_file)
|
|
246
|
+
_apply_app_context_to_env(app_ctx)
|
|
247
|
+
app_instance = app_ctx.app
|
|
248
|
+
|
|
249
|
+
web_root = app_instance.codegen.cfg.web_root
|
|
250
|
+
if not web_root.exists():
|
|
251
|
+
console.log(f"❌ Directory not found: {web_root.absolute()}")
|
|
252
|
+
raise typer.Exit(1)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
to_add = check_web_dependencies(
|
|
256
|
+
web_root,
|
|
257
|
+
pulse_version=PULSE_PY_VERSION,
|
|
258
|
+
)
|
|
259
|
+
except DependencyResolutionError as exc:
|
|
260
|
+
console.log(f"❌ {exc}")
|
|
261
|
+
raise typer.Exit(1) from None
|
|
262
|
+
except DependencyError as exc:
|
|
263
|
+
console.log(f"❌ {exc}")
|
|
264
|
+
raise typer.Exit(1) from None
|
|
265
|
+
|
|
266
|
+
if not to_add:
|
|
267
|
+
console.log("✅ Web dependencies are in sync")
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
console.log("📦 Web dependencies are out of sync:")
|
|
271
|
+
for pkg in to_add:
|
|
272
|
+
console.log(f" - {pkg}")
|
|
273
|
+
|
|
274
|
+
if not fix:
|
|
275
|
+
console.log("💡 Run 'pulse check --fix' to install missing dependencies")
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
# Apply fix
|
|
279
|
+
try:
|
|
280
|
+
dep_plan = prepare_web_dependencies(
|
|
281
|
+
web_root,
|
|
282
|
+
pulse_version=PULSE_PY_VERSION,
|
|
283
|
+
)
|
|
284
|
+
if dep_plan:
|
|
285
|
+
_run_dependency_plan(console, web_root, dep_plan)
|
|
286
|
+
console.log("✅ Web dependencies synced successfully")
|
|
287
|
+
except subprocess.CalledProcessError:
|
|
288
|
+
console.log("❌ Failed to install web dependencies with Bun.")
|
|
289
|
+
raise typer.Exit(1) from None
|
|
290
|
+
|
|
291
|
+
|
|
222
292
|
def build_uvicorn_command(
|
|
223
293
|
*,
|
|
224
294
|
app_ctx: AppLoadResult,
|
|
@@ -245,7 +315,7 @@ def build_uvicorn_command(
|
|
|
245
315
|
"--factory",
|
|
246
316
|
]
|
|
247
317
|
|
|
248
|
-
if reload_enabled
|
|
318
|
+
if reload_enabled:
|
|
249
319
|
args.append("--reload")
|
|
250
320
|
args.extend(["--reload-include", "*.css"])
|
|
251
321
|
app_dir = app_ctx.app_dir or Path.cwd()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from collections.abc import Callable, Iterable, Sequence
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
from pathlib import Path
|
|
@@ -18,6 +19,53 @@ from pulse.cli.packages import (
|
|
|
18
19
|
from pulse.react_component import ReactComponent, registered_react_components
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def convert_pep440_to_semver(python_version: str) -> str:
|
|
23
|
+
"""Convert PEP 440 version format to NPM semver format.
|
|
24
|
+
|
|
25
|
+
PEP 440 formats:
|
|
26
|
+
- 0.1.37a1 -> 0.1.37-alpha.1
|
|
27
|
+
- 0.1.37b1 -> 0.1.37-beta.1
|
|
28
|
+
- 0.1.37rc1 -> 0.1.37-rc.1
|
|
29
|
+
- 0.1.37.dev1 -> 0.1.37-dev.1
|
|
30
|
+
|
|
31
|
+
Non-pre-release versions are returned unchanged.
|
|
32
|
+
"""
|
|
33
|
+
# Match pre-release patterns: version followed by a/b/rc/dev + number
|
|
34
|
+
# PEP 440: a1, b1, rc1, dev1, alpha1, beta1, etc.
|
|
35
|
+
pattern = r"^(\d+\.\d+\.\d+)([a-z]+)(\d+)$"
|
|
36
|
+
match = re.match(pattern, python_version)
|
|
37
|
+
|
|
38
|
+
if match:
|
|
39
|
+
base_version = match.group(1)
|
|
40
|
+
prerelease_type = match.group(2)
|
|
41
|
+
prerelease_num = match.group(3)
|
|
42
|
+
|
|
43
|
+
# Map PEP 440 prerelease types to NPM semver
|
|
44
|
+
type_map = {
|
|
45
|
+
"a": "alpha",
|
|
46
|
+
"alpha": "alpha",
|
|
47
|
+
"b": "beta",
|
|
48
|
+
"beta": "beta",
|
|
49
|
+
"rc": "rc",
|
|
50
|
+
"c": "rc", # PEP 440 also allows 'c' for release candidate
|
|
51
|
+
"dev": "dev",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
npm_type = type_map.get(prerelease_type.lower(), prerelease_type)
|
|
55
|
+
return f"{base_version}-{npm_type}.{prerelease_num}"
|
|
56
|
+
|
|
57
|
+
# Also handle .dev format (e.g., 0.1.37.dev1)
|
|
58
|
+
pattern2 = r"^(\d+\.\d+\.\d+)\.dev(\d+)$"
|
|
59
|
+
match2 = re.match(pattern2, python_version)
|
|
60
|
+
if match2:
|
|
61
|
+
base_version = match2.group(1)
|
|
62
|
+
dev_num = match2.group(2)
|
|
63
|
+
return f"{base_version}-dev.{dev_num}"
|
|
64
|
+
|
|
65
|
+
# No pre-release, return as-is
|
|
66
|
+
return python_version
|
|
67
|
+
|
|
68
|
+
|
|
21
69
|
class DependencyError(RuntimeError):
|
|
22
70
|
"""Base error for dependency preparation failures."""
|
|
23
71
|
|
|
@@ -38,15 +86,15 @@ class DependencyPlan:
|
|
|
38
86
|
to_add: Sequence[str]
|
|
39
87
|
|
|
40
88
|
|
|
41
|
-
def
|
|
89
|
+
def get_required_dependencies(
|
|
42
90
|
web_root: Path,
|
|
43
91
|
*,
|
|
44
92
|
pulse_version: str,
|
|
45
93
|
component_provider: Callable[
|
|
46
94
|
[], Iterable[ReactComponent[Any]]
|
|
47
95
|
] = registered_react_components,
|
|
48
|
-
) ->
|
|
49
|
-
"""
|
|
96
|
+
) -> dict[str, str | None]:
|
|
97
|
+
"""Get the required dependencies for a Pulse app."""
|
|
50
98
|
if not web_root.exists():
|
|
51
99
|
raise DependencyError(f"Directory not found: {web_root}")
|
|
52
100
|
|
|
@@ -102,13 +150,30 @@ def prepare_web_dependencies(
|
|
|
102
150
|
]:
|
|
103
151
|
desired.setdefault(pkg, "^7")
|
|
104
152
|
|
|
153
|
+
return desired
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def check_web_dependencies(
|
|
157
|
+
web_root: Path,
|
|
158
|
+
*,
|
|
159
|
+
pulse_version: str,
|
|
160
|
+
component_provider: Callable[
|
|
161
|
+
[], Iterable[ReactComponent[Any]]
|
|
162
|
+
] = registered_react_components,
|
|
163
|
+
) -> list[str]:
|
|
164
|
+
"""Check if web dependencies are in sync and return list of packages that need to be added/updated."""
|
|
165
|
+
desired = get_required_dependencies(
|
|
166
|
+
web_root=web_root,
|
|
167
|
+
pulse_version=pulse_version,
|
|
168
|
+
component_provider=component_provider,
|
|
169
|
+
)
|
|
105
170
|
pkg_json = load_package_json(web_root)
|
|
106
171
|
|
|
107
172
|
to_add: list[str] = []
|
|
108
173
|
for name, req_ver in sorted(desired.items()):
|
|
109
174
|
effective = req_ver
|
|
110
175
|
if name == "pulse-ui-client":
|
|
111
|
-
effective = pulse_version
|
|
176
|
+
effective = convert_pep440_to_semver(pulse_version)
|
|
112
177
|
|
|
113
178
|
existing = get_pkg_spec(pkg_json, name)
|
|
114
179
|
if existing is None:
|
|
@@ -123,6 +188,24 @@ def prepare_web_dependencies(
|
|
|
123
188
|
|
|
124
189
|
to_add.append(f"{name}@{effective}" if effective else name)
|
|
125
190
|
|
|
191
|
+
return to_add
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def prepare_web_dependencies(
|
|
195
|
+
web_root: Path,
|
|
196
|
+
*,
|
|
197
|
+
pulse_version: str,
|
|
198
|
+
component_provider: Callable[
|
|
199
|
+
[], Iterable[ReactComponent[Any]]
|
|
200
|
+
] = registered_react_components,
|
|
201
|
+
) -> DependencyPlan | None:
|
|
202
|
+
"""Inspect registered components and return the Bun command needed to sync dependencies."""
|
|
203
|
+
to_add = check_web_dependencies(
|
|
204
|
+
web_root=web_root,
|
|
205
|
+
pulse_version=pulse_version,
|
|
206
|
+
component_provider=component_provider,
|
|
207
|
+
)
|
|
208
|
+
|
|
126
209
|
if to_add:
|
|
127
210
|
return DependencyPlan(command=["bun", "add", *to_add], to_add=to_add)
|
|
128
211
|
|
|
@@ -155,10 +155,15 @@ def cors_options(mode: "PulseMode", server_address: str) -> CORSOptions:
|
|
|
155
155
|
}
|
|
156
156
|
if mode == "subdomains":
|
|
157
157
|
base = _base_domain(host)
|
|
158
|
+
# Escape dots in base domain for regex (doesn't affect localhost since it has no dots)
|
|
159
|
+
base = base.replace(".", r"\.")
|
|
160
|
+
# Allow any subdomain and any port for the base domain
|
|
158
161
|
opts["allow_origin_regex"] = rf"^https?://([a-z0-9-]+\\.)?{base}(:\\d+)?$"
|
|
159
162
|
return opts
|
|
160
163
|
elif mode == "single-server":
|
|
161
164
|
# For single-server mode, allow same origin
|
|
165
|
+
# Escape dots in host for regex (doesn't affect localhost since it has no dots)
|
|
166
|
+
host = host.replace(".", r"\.")
|
|
162
167
|
opts["allow_origin_regex"] = rf"^https?://{host}(:\\d+)?$"
|
|
163
168
|
return opts
|
|
164
169
|
else:
|
|
@@ -39,13 +39,12 @@ class EnvVars:
|
|
|
39
39
|
else:
|
|
40
40
|
os.environ[key] = value
|
|
41
41
|
|
|
42
|
-
# Pulse mode
|
|
43
42
|
@property
|
|
44
43
|
def pulse_env(self) -> PulseEnv:
|
|
45
44
|
value = (self._get(ENV_PULSE_MODE) or "dev").lower()
|
|
46
45
|
if value not in ("dev", "ci", "prod"):
|
|
47
46
|
value = "dev"
|
|
48
|
-
return value
|
|
47
|
+
return value
|
|
49
48
|
|
|
50
49
|
@pulse_env.setter
|
|
51
50
|
def pulse_env(self, value: PulseEnv) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/templates/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulse_framework-0.1.38a1 → pulse_framework-0.1.38a2}/src/pulse/codegen/templates/routes_ts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|