pulse-framework 0.1.51__py3-none-any.whl → 0.1.53__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 (84) hide show
  1. pulse/__init__.py +542 -562
  2. pulse/_examples.py +29 -0
  3. pulse/app.py +0 -14
  4. pulse/cli/cmd.py +96 -80
  5. pulse/cli/dependencies.py +10 -41
  6. pulse/cli/folder_lock.py +3 -3
  7. pulse/cli/helpers.py +40 -67
  8. pulse/cli/logging.py +102 -0
  9. pulse/cli/packages.py +16 -0
  10. pulse/cli/processes.py +40 -23
  11. pulse/codegen/codegen.py +70 -35
  12. pulse/codegen/js.py +2 -4
  13. pulse/codegen/templates/route.py +94 -146
  14. pulse/component.py +115 -0
  15. pulse/components/for_.py +1 -1
  16. pulse/components/if_.py +1 -1
  17. pulse/components/react_router.py +16 -22
  18. pulse/{html → dom}/events.py +1 -1
  19. pulse/{html → dom}/props.py +6 -6
  20. pulse/{html → dom}/tags.py +11 -11
  21. pulse/dom/tags.pyi +480 -0
  22. pulse/form.py +7 -6
  23. pulse/hooks/init.py +1 -13
  24. pulse/js/__init__.py +37 -41
  25. pulse/js/__init__.pyi +22 -2
  26. pulse/js/_types.py +5 -3
  27. pulse/js/array.py +121 -38
  28. pulse/js/console.py +9 -9
  29. pulse/js/date.py +22 -19
  30. pulse/js/document.py +8 -4
  31. pulse/js/error.py +12 -14
  32. pulse/js/json.py +4 -3
  33. pulse/js/map.py +17 -7
  34. pulse/js/math.py +2 -2
  35. pulse/js/navigator.py +4 -4
  36. pulse/js/number.py +8 -8
  37. pulse/js/object.py +9 -13
  38. pulse/js/promise.py +25 -9
  39. pulse/js/regexp.py +6 -6
  40. pulse/js/set.py +20 -8
  41. pulse/js/string.py +7 -7
  42. pulse/js/weakmap.py +6 -6
  43. pulse/js/weakset.py +6 -6
  44. pulse/js/window.py +17 -14
  45. pulse/messages.py +1 -4
  46. pulse/react_component.py +3 -1001
  47. pulse/render_session.py +74 -66
  48. pulse/renderer.py +311 -238
  49. pulse/routing.py +1 -10
  50. pulse/transpiler/__init__.py +84 -114
  51. pulse/transpiler/builtins.py +661 -343
  52. pulse/transpiler/errors.py +78 -2
  53. pulse/transpiler/function.py +463 -133
  54. pulse/transpiler/id.py +18 -0
  55. pulse/transpiler/imports.py +230 -325
  56. pulse/transpiler/js_module.py +218 -209
  57. pulse/transpiler/modules/__init__.py +16 -13
  58. pulse/transpiler/modules/asyncio.py +45 -26
  59. pulse/transpiler/modules/json.py +12 -8
  60. pulse/transpiler/modules/math.py +161 -216
  61. pulse/transpiler/modules/pulse/__init__.py +5 -0
  62. pulse/transpiler/modules/pulse/tags.py +231 -0
  63. pulse/transpiler/modules/typing.py +33 -28
  64. pulse/transpiler/nodes.py +1607 -923
  65. pulse/transpiler/py_module.py +118 -95
  66. pulse/transpiler/react_component.py +51 -0
  67. pulse/transpiler/transpiler.py +593 -437
  68. pulse/transpiler/vdom.py +255 -0
  69. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/METADATA +1 -1
  70. pulse_framework-0.1.53.dist-info/RECORD +120 -0
  71. pulse/html/tags.pyi +0 -470
  72. pulse/transpiler/constants.py +0 -110
  73. pulse/transpiler/context.py +0 -26
  74. pulse/transpiler/ids.py +0 -16
  75. pulse/transpiler/modules/re.py +0 -466
  76. pulse/transpiler/modules/tags.py +0 -268
  77. pulse/transpiler/utils.py +0 -4
  78. pulse/vdom.py +0 -599
  79. pulse_framework-0.1.51.dist-info/RECORD +0 -119
  80. /pulse/{html → dom}/__init__.py +0 -0
  81. /pulse/{html → dom}/elements.py +0 -0
  82. /pulse/{html → dom}/svg.py +0 -0
  83. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/WHEEL +0 -0
  84. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/entry_points.txt +0 -0
pulse/_examples.py ADDED
@@ -0,0 +1,29 @@
1
+ def square(x: float) -> float:
2
+ """Return the square of x."""
3
+ return x * x
4
+
5
+
6
+ def cube(x: float) -> float:
7
+ """Return the cube of x."""
8
+ return x * x * x
9
+
10
+
11
+ def factorial(n: int) -> int:
12
+ """Calculate factorial recursively."""
13
+ if n <= 1:
14
+ return 1
15
+ return n * factorial(n - 1)
16
+
17
+
18
+ def is_even(n: int) -> bool:
19
+ """Check if n is even."""
20
+ return n % 2 == 0
21
+
22
+
23
+ def clamp(value: float, min_val: float, max_val: float) -> float:
24
+ """Clamp value between min_val and max_val."""
25
+ if value < min_val:
26
+ return min_val
27
+ if value > max_val:
28
+ return max_val
29
+ return value
pulse/app.py CHANGED
@@ -69,7 +69,6 @@ from pulse.middleware import (
69
69
  )
70
70
  from pulse.plugin import Plugin
71
71
  from pulse.proxy import ReactProxy
72
- from pulse.react_component import ReactComponent, registered_react_components
73
72
  from pulse.render_session import RenderSession
74
73
  from pulse.request import PulseRequest
75
74
  from pulse.routing import Layout, Route, RouteTree
@@ -208,8 +207,6 @@ class App:
208
207
  for plugin in self.plugins:
209
208
  all_routes.extend(plugin.routes())
210
209
 
211
- # Auto-add React components to all routes
212
- add_react_components(all_routes, registered_react_components())
213
210
  # RouteTree filters routes based on dev flag and environment during construction
214
211
  self.routes = RouteTree(all_routes)
215
212
  self.not_found = not_found
@@ -988,14 +985,3 @@ class App:
988
985
  # We don't want to wait for this to resolve
989
986
  create_task(render.call_api(f"{self.api_prefix}/set-cookies", method="GET"))
990
987
  sess.scheduled_cookie_refresh = True
991
-
992
-
993
- def add_react_components(
994
- routes: Sequence[Route | Layout],
995
- components: list[ReactComponent[Any]],
996
- ):
997
- for route in routes:
998
- if route.components is None:
999
- route.components = components
1000
- if route.children:
1001
- add_react_components(route.children, components)
pulse/cli/cmd.py CHANGED
@@ -15,7 +15,6 @@ from pathlib import Path
15
15
  from typing import Callable, cast
16
16
 
17
17
  import typer
18
- from rich.console import Console
19
18
 
20
19
  from pulse.cli.dependencies import (
21
20
  DependencyError,
@@ -26,6 +25,7 @@ from pulse.cli.dependencies import (
26
25
  )
27
26
  from pulse.cli.folder_lock import FolderLock
28
27
  from pulse.cli.helpers import load_app_from_target
28
+ from pulse.cli.logging import CLILogger
29
29
  from pulse.cli.models import AppLoadResult, CommandSpec
30
30
  from pulse.cli.processes import execute_commands
31
31
  from pulse.cli.secrets import resolve_dev_secret
@@ -65,9 +65,11 @@ def run(
65
65
  ),
66
66
  port: int = typer.Option(8000, "--port", help="Port uvicorn binds to"),
67
67
  # Env flags
68
- dev: bool = typer.Option(False, "--dev", help="Run in development env"),
69
- ci: bool = typer.Option(False, "--ci", help="Run in CI env"),
70
- prod: bool = typer.Option(False, "--prod", help="Run in production env"),
68
+ dev: bool = typer.Option(False, "--dev", help="Run in development mode"),
69
+ prod: bool = typer.Option(False, "--prod", help="Run in production mode"),
70
+ plain: bool = typer.Option(
71
+ False, "--plain", help="Use plain output without colors or emojis"
72
+ ),
71
73
  server_only: bool = typer.Option(False, "--server-only", "--backend-only"),
72
74
  web_only: bool = typer.Option(False, "--web-only"),
73
75
  react_server_address: str | None = typer.Option(
@@ -84,53 +86,49 @@ def run(
84
86
  """Run the Pulse server and web development server together."""
85
87
  extra_flags = list(ctx.args)
86
88
 
87
- env_flags = [
88
- name for flag, name in [(dev, "dev"), (ci, "ci"), (prod, "prod")] if flag
89
- ]
90
- if len(env_flags) > 1:
91
- typer.echo("❌ Please specify only one of --dev, --ci, or --prod.")
89
+ # Validate mode flags (dev is default if neither specified)
90
+ if dev and prod:
91
+ logger = CLILogger("dev", plain=plain)
92
+ logger.error("Please specify only one of --dev or --prod.")
92
93
  raise typer.Exit(1)
93
- if ci:
94
- typer.echo(
95
- "❌ --ci is not supported for 'pulse run'. Use 'pulse generate --ci' instead."
96
- )
97
- raise typer.Exit(1)
98
- if len(env_flags) == 1:
99
- env.pulse_env = cast(PulseEnv, env_flags[0])
94
+
95
+ # Set mode: prod if specified, otherwise dev (default)
96
+ mode: PulseEnv = "prod" if prod else "dev"
97
+ env.pulse_env = mode
98
+ logger = CLILogger(mode, plain=plain)
100
99
 
101
100
  # Turn on reload in dev only
102
101
  if reload is None:
103
102
  reload = env.pulse_env == "dev"
104
103
 
105
104
  if server_only and web_only:
106
- typer.echo("Cannot use --server-only and --web-only at the same time.")
105
+ logger.error("Cannot use --server-only and --web-only at the same time.")
107
106
  raise typer.Exit(1)
108
107
 
109
108
  if find_port:
110
109
  port = find_available_port(port)
111
110
 
112
- console = Console()
113
- console.print(f"📁 Loading app from: {app_file}")
114
- app_ctx = load_app_from_target(app_file)
111
+ logger.print(f"Loading app from {app_file}")
112
+ app_ctx = load_app_from_target(app_file, logger)
115
113
  _apply_app_context_to_env(app_ctx)
116
114
  app_instance = app_ctx.app
117
115
 
118
116
  is_single_server = app_instance.mode == "single-server"
119
117
  if is_single_server:
120
- console.print("🔧 [cyan]Single-server mode[/cyan]")
118
+ logger.print("Single-server mode")
121
119
 
122
120
  # In single-server + server-only mode, require explicit React server address
123
121
  if is_single_server and server_only:
124
122
  if not react_server_address:
125
- typer.echo(
126
- "--react-server-address is required when using single-server mode with --server-only."
123
+ logger.error(
124
+ "--react-server-address is required when using single-server mode with --server-only."
127
125
  )
128
126
  raise typer.Exit(1)
129
127
  os.environ[ENV_PULSE_REACT_SERVER_ADDRESS] = react_server_address
130
128
 
131
129
  web_root = app_instance.codegen.cfg.web_root
132
130
  if not web_root.exists() and not server_only:
133
- console.log(f"Directory not found: {web_root.absolute()}")
131
+ logger.error(f"Directory not found: {web_root.absolute()}")
134
132
  raise typer.Exit(1)
135
133
 
136
134
  dev_secret: str | None = None
@@ -146,10 +144,10 @@ def run(
146
144
  pulse_version=PULSE_PY_VERSION,
147
145
  )
148
146
  except DependencyResolutionError as exc:
149
- console.log(f"❌ {exc}")
147
+ logger.error(str(exc))
150
148
  raise typer.Exit(1) from None
151
149
  except DependencyError as exc:
152
- console.log(f"❌ {exc}")
150
+ logger.error(str(exc))
153
151
  raise typer.Exit(1) from None
154
152
 
155
153
  if to_add:
@@ -159,9 +157,9 @@ def run(
159
157
  pulse_version=PULSE_PY_VERSION,
160
158
  )
161
159
  if dep_plan:
162
- _run_dependency_plan(console, web_root, dep_plan)
160
+ _run_dependency_plan(logger, web_root, dep_plan)
163
161
  except subprocess.CalledProcessError:
164
- console.log("Failed to install web dependencies with Bun.")
162
+ logger.error("Failed to install web dependencies with Bun.")
165
163
  raise typer.Exit(1) from None
166
164
 
167
165
  server_args = extra_flags if not web_only else []
@@ -199,12 +197,7 @@ def run(
199
197
  announced = True
200
198
  protocol = "http" if address in ("127.0.0.1", "localhost") else "https"
201
199
  server_url = f"{protocol}://{address}:{port}"
202
- console.print("")
203
- console.print(
204
- f"✨ [bold green]Pulse running at:[/bold green] [bold cyan][link={server_url}]{server_url}[/link][/bold cyan]"
205
- )
206
- console.print(f" [dim]API: {server_url}/_pulse/...[/dim]")
207
- console.print("")
200
+ logger.write_ready_announcement(address, port, server_url)
208
201
 
209
202
  # Build web command first (when needed) so we can set PULSE_REACT_SERVER_ADDRESS
210
203
  # before building the uvicorn command, which needs that env var
@@ -231,7 +224,6 @@ def run(
231
224
  extra_args=server_args,
232
225
  dev_secret=dev_secret,
233
226
  server_only=server_only,
234
- console=console,
235
227
  web_root=web_root,
236
228
  verbose=verbose,
237
229
  ready_pattern=r"Application startup complete",
@@ -239,21 +231,15 @@ def run(
239
231
  )
240
232
  commands.append(server_cmd)
241
233
 
242
- # Only add tags in dev mode to avoid breaking structured output (e.g., CloudWatch EMF metrics)
243
- tag_colors = (
244
- {"server": "cyan", "web": "orange1"} if env.pulse_env == "dev" else None
245
- )
246
-
247
234
  with FolderLock(web_root):
248
235
  try:
249
236
  exit_code = execute_commands(
250
237
  commands,
251
- console=console,
252
- tag_colors=tag_colors,
238
+ tag_mode=logger.get_tag_mode(),
253
239
  )
254
240
  raise typer.Exit(exit_code)
255
241
  except RuntimeError as exc:
256
- console.log(f"❌ {exc}")
242
+ logger.error(str(exc))
257
243
  raise typer.Exit(1) from None
258
244
 
259
245
 
@@ -266,42 +252,54 @@ def generate(
266
252
  dev: bool = typer.Option(False, "--dev", help="Generate in development mode"),
267
253
  ci: bool = typer.Option(False, "--ci", help="Generate in CI mode"),
268
254
  prod: bool = typer.Option(False, "--prod", help="Generate in production mode"),
255
+ plain: bool = typer.Option(
256
+ False, "--plain", help="Use plain output without colors or emojis"
257
+ ),
269
258
  ):
270
259
  """Generate TypeScript routes without starting the server."""
271
- console = Console()
272
- console.log("🔄 Generating TypeScript routes...")
273
-
260
+ # Validate mode flags
274
261
  mode_flags = [
275
262
  name for flag, name in [(dev, "dev"), (ci, "ci"), (prod, "prod")] if flag
276
263
  ]
277
264
  if len(mode_flags) > 1:
278
- typer.echo("❌ Please specify only one of --dev, --ci, or --prod.")
265
+ logger = CLILogger("dev", plain=plain)
266
+ logger.error("Please specify only one of --dev, --ci, or --prod.")
279
267
  raise typer.Exit(1)
280
- if len(mode_flags) == 1:
281
- env.pulse_env = cast(PulseEnv, mode_flags[0])
282
268
 
283
- console.log(f"📁 Loading routes from: {app_file}")
269
+ # Set mode: use specified mode, otherwise dev (default)
270
+ mode: PulseEnv = cast(PulseEnv, mode_flags[0]) if mode_flags else "dev"
271
+ env.pulse_env = mode
272
+ logger = CLILogger(mode, plain=plain)
273
+
274
+ logger.print(f"Generating routes from {app_file}")
284
275
  env.codegen_disabled = False
285
- app_ctx = load_app_from_target(app_file)
276
+ app_ctx = load_app_from_target(app_file, logger)
286
277
  _apply_app_context_to_env(app_ctx)
287
278
  app = app_ctx.app
288
- console.log(f"📋 Found {len(app.routes.flat_tree)} routes")
289
279
 
290
280
  # In CI or prod mode, server_address must be provided
291
281
  if (ci or prod) and not app.server_address:
292
- typer.echo(
293
- "server_address must be provided when generating in CI or production mode. "
282
+ logger.error(
283
+ "server_address must be provided when generating in CI or production mode. "
294
284
  + "Set it in your App constructor or via the PULSE_SERVER_ADDRESS environment variable."
295
285
  )
296
286
  raise typer.Exit(1)
297
287
 
298
288
  addr = app.server_address or "http://localhost:8000"
299
- app.run_codegen(addr)
289
+ try:
290
+ app.run_codegen(addr)
291
+ except Exception:
292
+ logger.error("Failed to generate routes")
293
+ logger.print_exception()
294
+ raise typer.Exit(1) from None
300
295
 
301
- if len(app.routes.flat_tree) > 0:
302
- console.log(f"✅ Generated {len(app.routes.flat_tree)} routes successfully!")
296
+ route_count = len(app.routes.flat_tree)
297
+ if route_count > 0:
298
+ logger.success(
299
+ f"Generated {route_count} route{'s' if route_count != 1 else ''}"
300
+ )
303
301
  else:
304
- console.log("⚠️ No routes found to generate")
302
+ logger.warning("No routes found")
305
303
 
306
304
 
307
305
  @cli.command("check")
@@ -312,18 +310,37 @@ def check(
312
310
  fix: bool = typer.Option(
313
311
  False, "--fix", help="Install missing or outdated dependencies"
314
312
  ),
313
+ # Mode flags
314
+ dev: bool = typer.Option(False, "--dev", help="Run in development mode"),
315
+ ci: bool = typer.Option(False, "--ci", help="Run in CI mode"),
316
+ prod: bool = typer.Option(False, "--prod", help="Run in production mode"),
317
+ plain: bool = typer.Option(
318
+ False, "--plain", help="Use plain output without colors or emojis"
319
+ ),
315
320
  ):
316
321
  """Check if web project dependencies are in sync with Pulse app requirements."""
317
- console = Console()
322
+ # Validate mode flags
323
+ mode_flags = [
324
+ name for flag, name in [(dev, "dev"), (ci, "ci"), (prod, "prod")] if flag
325
+ ]
326
+ if len(mode_flags) > 1:
327
+ logger = CLILogger("dev", plain=plain)
328
+ logger.error("Please specify only one of --dev, --ci, or --prod.")
329
+ raise typer.Exit(1)
318
330
 
319
- console.log(f"📁 Loading app from: {app_file}")
320
- app_ctx = load_app_from_target(app_file)
331
+ # Set mode: use specified mode, otherwise dev (default)
332
+ mode: PulseEnv = cast(PulseEnv, mode_flags[0]) if mode_flags else "dev"
333
+ env.pulse_env = mode
334
+ logger = CLILogger(mode, plain=plain)
335
+
336
+ logger.print(f"Checking dependencies for {app_file}")
337
+ app_ctx = load_app_from_target(app_file, logger)
321
338
  _apply_app_context_to_env(app_ctx)
322
339
  app_instance = app_ctx.app
323
340
 
324
341
  web_root = app_instance.codegen.cfg.web_root
325
342
  if not web_root.exists():
326
- console.log(f"Directory not found: {web_root.absolute()}")
343
+ logger.error(f"Directory not found: {web_root.absolute()}")
327
344
  raise typer.Exit(1)
328
345
 
329
346
  try:
@@ -332,22 +349,22 @@ def check(
332
349
  pulse_version=PULSE_PY_VERSION,
333
350
  )
334
351
  except DependencyResolutionError as exc:
335
- console.log(f"❌ {exc}")
352
+ logger.error(str(exc))
336
353
  raise typer.Exit(1) from None
337
354
  except DependencyError as exc:
338
- console.log(f"❌ {exc}")
355
+ logger.error(str(exc))
339
356
  raise typer.Exit(1) from None
340
357
 
341
358
  if not to_add:
342
- console.log(" Web dependencies are in sync")
359
+ logger.success("Dependencies in sync")
343
360
  return
344
361
 
345
- console.log("📦 Web dependencies are out of sync:")
362
+ logger.print("Missing dependencies:")
346
363
  for pkg in to_add:
347
- console.log(f" - {pkg}")
364
+ logger.print(f" {pkg}")
348
365
 
349
366
  if not fix:
350
- console.log("💡 Run 'pulse check --fix' to install missing dependencies")
367
+ logger.print("Run 'pulse check --fix' to install")
351
368
  return
352
369
 
353
370
  # Apply fix
@@ -357,10 +374,10 @@ def check(
357
374
  pulse_version=PULSE_PY_VERSION,
358
375
  )
359
376
  if dep_plan:
360
- _run_dependency_plan(console, web_root, dep_plan)
361
- console.log(" Web dependencies synced successfully")
377
+ _run_dependency_plan(logger, web_root, dep_plan)
378
+ logger.success("Dependencies synced")
362
379
  except subprocess.CalledProcessError:
363
- console.log("Failed to install web dependencies with Bun.")
380
+ logger.error("Failed to install web dependencies with Bun.")
364
381
  raise typer.Exit(1) from None
365
382
 
366
383
 
@@ -373,7 +390,6 @@ def build_uvicorn_command(
373
390
  extra_args: Sequence[str],
374
391
  dev_secret: str | None,
375
392
  server_only: bool,
376
- console: Console,
377
393
  web_root: Path,
378
394
  verbose: bool = False,
379
395
  ready_pattern: str | None = None,
@@ -500,13 +516,10 @@ def _apply_app_context_to_env(app_ctx: AppLoadResult) -> None:
500
516
 
501
517
 
502
518
  def _run_dependency_plan(
503
- console: Console, web_root: Path, plan: DependencyPlan
519
+ logger: CLILogger, web_root: Path, plan: DependencyPlan
504
520
  ) -> None:
505
- # command_display = " ".join(plan.command)
506
521
  if plan.to_add:
507
- console.log(f"📦 Adding/updating web dependencies in {web_root}")
508
- else:
509
- console.log(f"📦 Installing web dependencies in {web_root}")
522
+ logger.print(f"Installing dependencies in {web_root}")
510
523
  subprocess.run(plan.command, cwd=web_root, check=True)
511
524
 
512
525
 
@@ -514,10 +527,13 @@ def main():
514
527
  """Main CLI entry point."""
515
528
  try:
516
529
  cli()
530
+ except SystemExit:
531
+ # Let typer.Exit and sys.exit propagate normally (no traceback)
532
+ raise
517
533
  except Exception:
518
- console = Console()
519
- console.print_exception()
520
- raise typer.Exit(1) from None
534
+ logger = CLILogger(env.pulse_env)
535
+ logger.print_exception()
536
+ sys.exit(1)
521
537
 
522
538
 
523
539
  def production_flags():
pulse/cli/dependencies.py CHANGED
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from collections.abc import Callable, Iterable, Sequence
4
+ from collections.abc import Sequence
5
5
  from dataclasses import dataclass
6
6
  from pathlib import Path
7
- from typing import Any
8
7
 
9
8
  from pulse.cli.packages import (
10
9
  VersionConflict,
@@ -16,7 +15,7 @@ from pulse.cli.packages import (
16
15
  resolve_versions,
17
16
  spec_satisfies,
18
17
  )
19
- from pulse.react_component import ReactComponent, registered_react_components
18
+ from pulse.transpiler.imports import get_registered_imports
20
19
 
21
20
 
22
21
  def convert_pep440_to_semver(python_version: str) -> str:
@@ -90,51 +89,29 @@ def get_required_dependencies(
90
89
  web_root: Path,
91
90
  *,
92
91
  pulse_version: str,
93
- component_provider: Callable[
94
- [], Iterable[ReactComponent[Any]]
95
- ] = registered_react_components,
96
92
  ) -> dict[str, str | None]:
97
93
  """Get the required dependencies for a Pulse app."""
98
94
  if not web_root.exists():
99
95
  raise DependencyError(f"Directory not found: {web_root}")
100
96
 
101
- try:
102
- components = list(component_provider())
103
- except Exception as exc:
104
- raise DependencyError("Unable to inspect registered React components") from exc
105
-
106
97
  constraints: dict[str, list[str | None]] = {
107
98
  "pulse-ui-client": [pulse_version],
108
99
  }
109
100
 
110
- for comp in components:
111
- src = getattr(comp, "src", None)
112
- component_pkg_name: str | None = None
113
- if src:
101
+ # New transpiler v2 imports
102
+ for imp in get_registered_imports():
103
+ if imp.src:
114
104
  try:
115
- spec = parse_install_spec(src)
105
+ spec = parse_install_spec(imp.src)
116
106
  except ValueError as exc:
107
+ # We might want to be more lenient here or at least log it,
108
+ # but following existing pattern of raising DependencyError
117
109
  raise DependencyError(str(exc)) from None
118
110
  if spec:
119
111
  name_only, ver = parse_dependency_spec(spec)
120
112
  constraints.setdefault(name_only, []).append(ver)
121
- component_pkg_name = name_only
122
-
123
- comp_version = getattr(comp, "version", None)
124
- if comp_version and component_pkg_name:
125
- constraints.setdefault(component_pkg_name, []).append(comp_version)
126
-
127
- for extra in getattr(comp, "extra_imports", []):
128
- extra_src = getattr(extra, "src", None) if extra is not None else None
129
- if not extra_src:
130
- continue
131
- try:
132
- spec2 = parse_install_spec(extra_src)
133
- except ValueError as exc:
134
- raise DependencyError(str(exc)) from None
135
- if spec2:
136
- name_only2, ver2 = parse_dependency_spec(spec2)
137
- constraints.setdefault(name_only2, []).append(ver2)
113
+ if imp.version:
114
+ constraints.setdefault(name_only, []).append(imp.version)
138
115
 
139
116
  try:
140
117
  resolved = resolve_versions(constraints)
@@ -157,15 +134,11 @@ def check_web_dependencies(
157
134
  web_root: Path,
158
135
  *,
159
136
  pulse_version: str,
160
- component_provider: Callable[
161
- [], Iterable[ReactComponent[Any]]
162
- ] = registered_react_components,
163
137
  ) -> list[str]:
164
138
  """Check if web dependencies are in sync and return list of packages that need to be added/updated."""
165
139
  desired = get_required_dependencies(
166
140
  web_root=web_root,
167
141
  pulse_version=pulse_version,
168
- component_provider=component_provider,
169
142
  )
170
143
  pkg_json = load_package_json(web_root)
171
144
 
@@ -195,15 +168,11 @@ def prepare_web_dependencies(
195
168
  web_root: Path,
196
169
  *,
197
170
  pulse_version: str,
198
- component_provider: Callable[
199
- [], Iterable[ReactComponent[Any]]
200
- ] = registered_react_components,
201
171
  ) -> DependencyPlan | None:
202
172
  """Inspect registered components and return the Bun command needed to sync dependencies."""
203
173
  to_add = check_web_dependencies(
204
174
  web_root=web_root,
205
175
  pulse_version=pulse_version,
206
- component_provider=component_provider,
207
176
  )
208
177
 
209
178
  if to_add:
pulse/cli/folder_lock.py CHANGED
@@ -90,7 +90,7 @@ def _remove_lock_file(lock_path: Path) -> None:
90
90
  pass
91
91
 
92
92
 
93
- def lock_path_for_web_root(web_root: Path, filename: str = ".pulse.lock") -> Path:
93
+ def lock_path_for_web_root(web_root: Path, filename: str = ".pulse/lock") -> Path:
94
94
  """Return the lock file path for a given web root."""
95
95
  return Path(web_root) / filename
96
96
 
@@ -110,13 +110,13 @@ class FolderLock:
110
110
  pass
111
111
  """
112
112
 
113
- def __init__(self, web_root: Path, *, filename: str = ".pulse.lock"):
113
+ def __init__(self, web_root: Path, *, filename: str = ".pulse/lock"):
114
114
  """
115
115
  Initialize FolderLock.
116
116
 
117
117
  Args:
118
118
  web_root: Path to the web root directory
119
- filename: Name of the lock file (default: ".pulse.lock")
119
+ filename: Name of the lock file (default: ".pulse/lock")
120
120
  """
121
121
  self.lock_path: Path = lock_path_for_web_root(web_root, filename)
122
122