jac-client 0.2.8__py3-none-any.whl → 0.2.9__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 (52) hide show
  1. jac_client/examples/all-in-one/{app.jac → main.jac} +5 -5
  2. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +8 -1
  3. jac_client/examples/all-in-one/pages/FeaturesTest.jac +16 -1
  4. jac_client/examples/all-in-one/pages/{FeaturesTest.cl.jac → features_test_ui.cl.jac} +11 -0
  5. jac_client/examples/all-in-one/pages/nestedDemo.jac +1 -1
  6. jac_client/examples/all-in-one/pages/notFound.jac +2 -7
  7. jac_client/plugin/cli.jac +162 -435
  8. jac_client/plugin/client.jac +25 -0
  9. jac_client/plugin/client_runtime.cl.jac +5 -1
  10. jac_client/plugin/impl/client.impl.jac +96 -55
  11. jac_client/plugin/impl/client_runtime.impl.jac +154 -0
  12. jac_client/plugin/plugin_config.jac +243 -15
  13. jac_client/plugin/src/config_loader.jac +1 -0
  14. jac_client/plugin/src/impl/compiler.impl.jac +1 -1
  15. jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
  16. jac_client/plugin/src/impl/vite_bundler.impl.jac +97 -16
  17. jac_client/plugin/src/vite_bundler.jac +6 -0
  18. jac_client/plugin/utils/__init__.jac +1 -0
  19. jac_client/plugin/utils/impl/node_installer.impl.jac +249 -0
  20. jac_client/plugin/utils/node_installer.jac +41 -0
  21. jac_client/templates/client.jacpack +72 -0
  22. jac_client/templates/fullstack.jacpack +61 -0
  23. jac_client/tests/conftest.py +48 -7
  24. jac_client/tests/test_cli.py +184 -70
  25. jac_client/tests/test_e2e.py +232 -0
  26. jac_client/tests/test_helpers.py +65 -0
  27. jac_client/tests/test_it.py +91 -135
  28. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/METADATA +4 -4
  29. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/RECORD +52 -45
  30. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/WHEEL +1 -1
  31. /jac_client/examples/all-in-one/pages/{BudgetPlanner.cl.jac → budget_planner_ui.cl.jac} +0 -0
  32. /jac_client/examples/asset-serving/css-with-image/{src/app.jac → main.jac} +0 -0
  33. /jac_client/examples/asset-serving/image-asset/{src/app.jac → main.jac} +0 -0
  34. /jac_client/examples/asset-serving/import-alias/{src/app.jac → main.jac} +0 -0
  35. /jac_client/examples/basic/{src/app.jac → main.jac} +0 -0
  36. /jac_client/examples/basic-auth/{src/app.jac → main.jac} +0 -0
  37. /jac_client/examples/basic-auth-with-router/{src/app.jac → main.jac} +0 -0
  38. /jac_client/examples/basic-full-stack/{src/app.jac → main.jac} +0 -0
  39. /jac_client/examples/css-styling/js-styling/{src/app.jac → main.jac} +0 -0
  40. /jac_client/examples/css-styling/material-ui/{src/app.jac → main.jac} +0 -0
  41. /jac_client/examples/css-styling/pure-css/{src/app.jac → main.jac} +0 -0
  42. /jac_client/examples/css-styling/sass-example/{src/app.jac → main.jac} +0 -0
  43. /jac_client/examples/css-styling/styled-components/{src/app.jac → main.jac} +0 -0
  44. /jac_client/examples/css-styling/tailwind-example/{src/app.jac → main.jac} +0 -0
  45. /jac_client/examples/full-stack-with-auth/{src/app.jac → main.jac} +0 -0
  46. /jac_client/examples/little-x/{src/app.jac → main.jac} +0 -0
  47. /jac_client/examples/nested-folders/nested-advance/{src/app.jac → main.jac} +0 -0
  48. /jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +0 -0
  49. /jac_client/examples/ts-support/{src/app.jac → main.jac} +0 -0
  50. /jac_client/examples/with-router/{src/app.jac → main.jac} +0 -0
  51. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/entry_points.txt +0 -0
  52. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/top_level.txt +0 -0
@@ -6,87 +6,21 @@ import gc
6
6
  import json
7
7
  import os
8
8
  import shutil
9
- import socket
10
- import sys
11
9
  import tempfile
12
10
  import time
13
11
  from http.client import RemoteDisconnected
14
- from pathlib import Path
15
12
  from subprocess import PIPE, Popen, run
16
13
  from urllib.error import HTTPError, URLError
17
14
  from urllib.request import Request, urlopen
18
15
 
19
16
  import pytest
20
17
 
21
- from jaclang.pycore.runtime import JacRuntime as Jac
22
-
23
-
24
- def _get_jac_command() -> list[str]:
25
- """Get the jac command with proper path handling."""
26
- jac_path = shutil.which("jac")
27
- if jac_path:
28
- return [jac_path]
29
- return [sys.executable, "-m", "jaclang"]
30
-
31
-
32
- def _get_env_with_npm() -> dict[str, str]:
33
- """Get environment dict with npm in PATH."""
34
- env = os.environ.copy()
35
- npm_path = shutil.which("npm")
36
- if npm_path:
37
- npm_dir = str(Path(npm_path).parent)
38
- current_path = env.get("PATH", "")
39
- if npm_dir not in current_path:
40
- env["PATH"] = f"{npm_dir}:{current_path}"
41
- # Also check common nvm locations
42
- nvm_dir = os.environ.get("NVM_DIR", os.path.expanduser("~/.nvm"))
43
- nvm_node_bin = Path(nvm_dir) / "versions" / "node"
44
- if nvm_node_bin.exists():
45
- for version_dir in nvm_node_bin.iterdir():
46
- bin_dir = version_dir / "bin"
47
- if bin_dir.exists() and (bin_dir / "npm").exists():
48
- current_path = env.get("PATH", "")
49
- if str(bin_dir) not in current_path:
50
- env["PATH"] = f"{bin_dir}:{current_path}"
51
- break
52
- return env
53
-
54
-
55
- @pytest.fixture(autouse=True)
56
- def reset_jac_machine():
57
- """Reset Jac machine before and after each test."""
58
- Jac.reset_machine()
59
- yield
60
- Jac.reset_machine()
61
-
62
-
63
- def _wait_for_port(
64
- host: str,
65
- port: int,
66
- timeout: float = 60.0,
67
- poll_interval: float = 0.5,
68
- ) -> None:
69
- """Block until a TCP port is accepting connections or timeout.
70
-
71
- Raises:
72
- TimeoutError: if the port is not accepting connections within timeout.
73
- """
74
- deadline = time.time() + timeout
75
- last_err: Exception | None = None
76
-
77
- while time.time() < deadline:
78
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
79
- sock.settimeout(poll_interval)
80
- try:
81
- sock.connect((host, port))
82
- return
83
- except OSError as exc: # Connection refused / timeout
84
- last_err = exc
85
- time.sleep(poll_interval)
86
-
87
- raise TimeoutError(
88
- f"Timed out waiting for {host}:{port} to become available. Last error: {last_err}"
89
- )
18
+ from .test_helpers import (
19
+ get_env_with_npm,
20
+ get_free_port,
21
+ get_jac_command,
22
+ wait_for_port,
23
+ )
90
24
 
91
25
 
92
26
  def _wait_for_endpoint(
@@ -150,7 +84,7 @@ def _wait_for_endpoint(
150
84
  def test_all_in_one_app_endpoints() -> None:
151
85
  """Create a Jac app, copy @all-in-one into it, install packages from jac.toml, then verify endpoints."""
152
86
  print(
153
- "[DEBUG] Starting test_all_in_one_app_endpoints using jac create --cl + @all-in-one"
87
+ "[DEBUG] Starting test_all_in_one_app_endpoints using jac create --use client + @all-in-one"
154
88
  )
155
89
 
156
90
  # Resolve the path to jac_client/examples/all-in-one relative to this test file.
@@ -171,9 +105,9 @@ def test_all_in_one_app_endpoints() -> None:
171
105
  print(f"[DEBUG] Changed working directory to {temp_dir}")
172
106
 
173
107
  # 1. Create a new Jac app via CLI (requires jac + jac-client plugin installed)
174
- print(f"[DEBUG] Running 'jac create --cl {app_name}'")
108
+ print(f"[DEBUG] Running 'jac create --use client {app_name}'")
175
109
  process = Popen(
176
- ["jac", "create", "--cl", app_name],
110
+ ["jac", "create", "--use", "client", app_name],
177
111
  stdin=PIPE,
178
112
  stdout=PIPE,
179
113
  stderr=PIPE,
@@ -183,21 +117,21 @@ def test_all_in_one_app_endpoints() -> None:
183
117
  returncode = process.returncode
184
118
 
185
119
  print(
186
- "[DEBUG] 'jac create --cl' completed "
120
+ "[DEBUG] 'jac create --use client' completed "
187
121
  f"returncode={returncode}\n"
188
122
  f"STDOUT:\n{stdout}\n"
189
123
  f"STDERR:\n{stderr}\n"
190
124
  )
191
125
 
192
- # If the currently installed `jac` CLI does not support `create --cl`,
126
+ # If the currently installed `jac` CLI does not support `create --use client`,
193
127
  # fail the test instead of skipping it.
194
- if returncode != 0 and "unrecognized arguments: --cl" in stderr:
128
+ if returncode != 0 and "unrecognized arguments: --use" in stderr:
195
129
  pytest.fail(
196
- "Test failed: installed `jac` CLI does not support `create --cl`."
130
+ "Test failed: installed `jac` CLI does not support `create --use client`."
197
131
  )
198
132
 
199
133
  assert returncode == 0, (
200
- f"jac create --cl failed\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}\n"
134
+ f"jac create --use client failed\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}\n"
201
135
  )
202
136
 
203
137
  project_path = os.path.join(temp_dir, app_name)
@@ -217,17 +151,17 @@ def test_all_in_one_app_endpoints() -> None:
217
151
  else:
218
152
  shutil.copy2(src, dst)
219
153
 
220
- # 3. Install packages from jac.toml using `jac add --cl`
154
+ # 3. Install packages from jac.toml using `jac add --npm`
221
155
  # This reads packages from jac.toml, generates package.json, and runs npm install
222
- print("[DEBUG] Running 'jac add --cl' to install packages from jac.toml")
156
+ print("[DEBUG] Running 'jac add --npm' to install packages from jac.toml")
223
157
  jac_add_result = run(
224
- ["jac", "add", "--cl"],
158
+ ["jac", "add", "--npm"],
225
159
  cwd=project_path,
226
160
  capture_output=True,
227
161
  text=True,
228
162
  )
229
163
  print(
230
- "[DEBUG] 'jac add --cl' completed "
164
+ "[DEBUG] 'jac add --npm' completed "
231
165
  f"returncode={jac_add_result.returncode}\n"
232
166
  f"STDOUT (truncated to 2000 chars):\n{jac_add_result.stdout[:2000]}\n"
233
167
  f"STDERR (truncated to 2000 chars):\n{jac_add_result.stderr[:2000]}\n"
@@ -235,28 +169,36 @@ def test_all_in_one_app_endpoints() -> None:
235
169
 
236
170
  if jac_add_result.returncode != 0:
237
171
  pytest.fail(
238
- f"Test failed: jac add --cl failed or npm is not available in PATH.\n"
172
+ f"Test failed: jac add --npm failed or npm is not available in PATH.\n"
239
173
  f"STDOUT:\n{jac_add_result.stdout}\n"
240
174
  f"STDERR:\n{jac_add_result.stderr}\n"
241
175
  )
242
176
 
243
- app_jac_path = os.path.join(project_path, "app.jac")
244
- assert os.path.isfile(app_jac_path), "all-in-one src/app.jac file missing"
177
+ app_jac_path = os.path.join(project_path, "main.jac")
178
+ assert os.path.isfile(app_jac_path), "all-in-one main.jac file missing"
245
179
 
246
- # 4. Start the server: `jac start src/app.jac`
180
+ # 4. Start the server: `jac start main.jac`
247
181
  # NOTE: We don't use text mode here, so `Popen` defaults to bytes.
248
182
  # Use `Popen[bytes]` in the type annotation to keep mypy happy.
249
183
  server: Popen[bytes] | None = None
184
+ # Use dynamic port allocation to avoid conflicts when running tests in parallel
185
+ server_port = get_free_port()
250
186
  try:
251
- print("[DEBUG] Starting server with 'jac start src/app.jac'")
187
+ print(
188
+ f"[DEBUG] Starting server with 'jac start main.jac -p {server_port}'"
189
+ )
252
190
  server = Popen(
253
- ["jac", "start", "src/app.jac"],
191
+ ["jac", "start", "main.jac", "-p", str(server_port)],
254
192
  cwd=project_path,
255
193
  )
256
194
  # Wait for localhost:8000 to become available
257
- print("[DEBUG] Waiting for server to be available on 127.0.0.1:8000")
258
- _wait_for_port("127.0.0.1", 8000, timeout=90.0)
259
- print("[DEBUG] Server is now accepting connections on 127.0.0.1:8000")
195
+ print(
196
+ f"[DEBUG] Waiting for server to be available on 127.0.0.1:{server_port}"
197
+ )
198
+ wait_for_port("127.0.0.1", server_port, timeout=90.0)
199
+ print(
200
+ f"[DEBUG] Server is now accepting connections on 127.0.0.1:{server_port}"
201
+ )
260
202
 
261
203
  # "/" – server up (serves client app HTML due to base_route_app="app")
262
204
  # Note: The root endpoint may return 503 while the client bundle is building.
@@ -264,7 +206,7 @@ def test_all_in_one_app_endpoints() -> None:
264
206
  try:
265
207
  print("[DEBUG] Sending GET request to root endpoint / (with retry)")
266
208
  root_bytes = _wait_for_endpoint(
267
- "http://127.0.0.1:8000",
209
+ f"http://127.0.0.1:{server_port}",
268
210
  timeout=120.0,
269
211
  poll_interval=2.0,
270
212
  request_timeout=30.0,
@@ -290,7 +232,7 @@ def test_all_in_one_app_endpoints() -> None:
290
232
  "[DEBUG] Sending GET request to /cl/app endpoint (with retry)"
291
233
  )
292
234
  page_bytes = _wait_for_endpoint(
293
- "http://127.0.0.1:8000/cl/app",
235
+ f"http://127.0.0.1:{server_port}/cl/app",
294
236
  timeout=120.0,
295
237
  poll_interval=2.0,
296
238
  request_timeout=30.0,
@@ -310,7 +252,7 @@ def test_all_in_one_app_endpoints() -> None:
310
252
  try:
311
253
  print("[DEBUG] Sending GET request to /cl/app#/nested endpoint")
312
254
  with urlopen(
313
- "http://127.0.0.1:8000/cl/app#/nested",
255
+ f"http://127.0.0.1:{server_port}/cl/app#/nested",
314
256
  timeout=200,
315
257
  ) as resp_nested:
316
258
  nested_body = resp_nested.read().decode(
@@ -336,7 +278,7 @@ def test_all_in_one_app_endpoints() -> None:
336
278
  try:
337
279
  print("[DEBUG] Sending GET request to /static/assets/burger.png")
338
280
  with urlopen(
339
- "http://127.0.0.1:8000/static/assets/burger.png",
281
+ f"http://127.0.0.1:{server_port}/static/assets/burger.png",
340
282
  timeout=20,
341
283
  ) as resp_png:
342
284
  png_bytes = resp_png.read()
@@ -362,7 +304,7 @@ def test_all_in_one_app_endpoints() -> None:
362
304
  "[DEBUG] Sending GET request to /workers/worker.js (with retry)"
363
305
  )
364
306
  worker_js_bytes = _wait_for_endpoint(
365
- "http://127.0.0.1:8000/workers/worker.js",
307
+ f"http://127.0.0.1:{server_port}/workers/worker.js",
366
308
  timeout=60.0,
367
309
  poll_interval=2.0,
368
310
  request_timeout=20.0,
@@ -384,13 +326,18 @@ def test_all_in_one_app_endpoints() -> None:
384
326
  f"Failed to GET /workers/worker.js after retries: {exc}"
385
327
  )
386
328
 
387
- # "/walker/get_server_message" – walkers are integrated and up and running
329
+ # POST /walker/get_server_message – walkers are integrated and up and running
388
330
  try:
389
- print("[DEBUG] Sending GET request to /walker/get_server_message")
390
- with urlopen(
391
- "http://127.0.0.1:8000/walker/get_server_message",
392
- timeout=20,
393
- ) as resp_walker:
331
+ print(
332
+ "[DEBUG] Sending POST request to /walker/get_server_message endpoint"
333
+ )
334
+ req = Request(
335
+ f"http://127.0.0.1:{server_port}/walker/get_server_message",
336
+ data=json.dumps({}).encode("utf-8"),
337
+ headers={"Content-Type": "application/json"},
338
+ method="POST",
339
+ )
340
+ with urlopen(req, timeout=20) as resp_walker:
394
341
  walker_body = resp_walker.read().decode(
395
342
  "utf-8", errors="ignore"
396
343
  )
@@ -400,12 +347,13 @@ def test_all_in_one_app_endpoints() -> None:
400
347
  f"Body (truncated to 500 chars):\n{walker_body[:500]}"
401
348
  )
402
349
  assert resp_walker.status == 200
403
- assert "get_server_message" in walker_body
404
- except (URLError, HTTPError) as exc:
350
+ # The walker reports "hello from a basic walker!"
351
+ assert "hello from a basic walker" in walker_body.lower()
352
+ except (URLError, HTTPError, RemoteDisconnected) as exc:
405
353
  print(
406
354
  f"[DEBUG] Error while requesting /walker/get_server_message: {exc}"
407
355
  )
408
- pytest.fail("Failed to GET /walker/get_server_message")
356
+ pytest.fail("Failed to POST /walker/get_server_message")
409
357
 
410
358
  # POST /walker/create_todo – create a Todo via walker HTTP API
411
359
  try:
@@ -416,7 +364,7 @@ def test_all_in_one_app_endpoints() -> None:
416
364
  "text": "Sample todo from all-in-one app",
417
365
  }
418
366
  req = Request(
419
- "http://127.0.0.1:8000/walker/create_todo",
367
+ f"http://127.0.0.1:{server_port}/walker/create_todo",
420
368
  data=json.dumps(payload).encode("utf-8"),
421
369
  headers={"Content-Type": "application/json"},
422
370
  method="POST",
@@ -447,7 +395,7 @@ def test_all_in_one_app_endpoints() -> None:
447
395
  "password": test_password,
448
396
  }
449
397
  req_register = Request(
450
- "http://127.0.0.1:8000/user/register",
398
+ f"http://127.0.0.1:{server_port}/user/register",
451
399
  data=json.dumps(register_payload).encode("utf-8"),
452
400
  headers={"Content-Type": "application/json"},
453
401
  method="POST",
@@ -488,7 +436,7 @@ def test_all_in_one_app_endpoints() -> None:
488
436
  "password": test_password,
489
437
  }
490
438
  req_login = Request(
491
- "http://127.0.0.1:8000/user/login",
439
+ f"http://127.0.0.1:{server_port}/user/login",
492
440
  data=json.dumps(login_payload).encode("utf-8"),
493
441
  headers={"Content-Type": "application/json"},
494
442
  method="POST",
@@ -524,7 +472,7 @@ def test_all_in_one_app_endpoints() -> None:
524
472
  "password": "wrong_password",
525
473
  }
526
474
  req_invalid_login = Request(
527
- "http://127.0.0.1:8000/user/login",
475
+ f"http://127.0.0.1:{server_port}/user/login",
528
476
  data=json.dumps(invalid_login_payload).encode("utf-8"),
529
477
  headers={"Content-Type": "application/json"},
530
478
  method="POST",
@@ -611,10 +559,10 @@ def test_all_in_one_app_endpoints() -> None:
611
559
 
612
560
 
613
561
  def test_default_client_app_renders() -> None:
614
- """Test that a default `jac create --cl` app renders correctly when served.
562
+ """Test that a default `jac create --use client` app renders correctly when served.
615
563
 
616
564
  This test validates the out-of-the-box experience:
617
- 1. Creates a new client app using `jac create --cl`
565
+ 1. Creates a new client app using `jac create --use client`
618
566
  2. Installs packages
619
567
  3. Starts the server
620
568
  4. Validates that the default app renders with expected content
@@ -631,11 +579,13 @@ def test_default_client_app_renders() -> None:
631
579
  print(f"[DEBUG] Changed working directory to {temp_dir}")
632
580
 
633
581
  # 1. Create a new default Jac client app
634
- jac_cmd = _get_jac_command()
635
- env = _get_env_with_npm()
636
- print(f"[DEBUG] Running '{' '.join(jac_cmd)} create --cl {app_name}'")
582
+ jac_cmd = get_jac_command()
583
+ env = get_env_with_npm()
584
+ print(
585
+ f"[DEBUG] Running '{' '.join(jac_cmd)} create --use client {app_name}'"
586
+ )
637
587
  process = Popen(
638
- [*jac_cmd, "create", "--cl", app_name],
588
+ [*jac_cmd, "create", "--use", "client", app_name],
639
589
  stdin=PIPE,
640
590
  stdout=PIPE,
641
591
  stderr=PIPE,
@@ -646,18 +596,18 @@ def test_default_client_app_renders() -> None:
646
596
  returncode = process.returncode
647
597
 
648
598
  print(
649
- f"[DEBUG] 'jac create --cl' completed returncode={returncode}\n"
599
+ f"[DEBUG] 'jac create --use client' completed returncode={returncode}\n"
650
600
  f"STDOUT:\n{stdout}\n"
651
601
  f"STDERR:\n{stderr}\n"
652
602
  )
653
603
 
654
- if returncode != 0 and "unrecognized arguments: --cl" in stderr:
604
+ if returncode != 0 and "unrecognized arguments: --use" in stderr:
655
605
  pytest.fail(
656
- "Test failed: installed `jac` CLI does not support `create --cl`."
606
+ "Test failed: installed `jac` CLI does not support `create --use client`."
657
607
  )
658
608
 
659
609
  assert returncode == 0, (
660
- f"jac create --cl failed\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}\n"
610
+ f"jac create --use client failed\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}\n"
661
611
  )
662
612
 
663
613
  project_path = os.path.join(temp_dir, app_name)
@@ -679,46 +629,52 @@ def test_default_client_app_renders() -> None:
679
629
  jac_toml_path = os.path.join(project_path, "jac.toml")
680
630
  assert os.path.isfile(jac_toml_path), "jac.toml should exist"
681
631
 
682
- # 2. Ensure packages are installed (jac create --cl should have done this)
683
- # If node_modules doesn't exist, run jac add --cl
632
+ # 2. Ensure packages are installed (jac create --use client should have done this)
633
+ # If node_modules doesn't exist, run jac add --npm
684
634
  node_modules_path = os.path.join(
685
635
  project_path, ".jac", "client", "node_modules"
686
636
  )
687
637
  if not os.path.isdir(node_modules_path):
688
- print("[DEBUG] node_modules not found, running 'jac add --cl'")
638
+ print("[DEBUG] node_modules not found, running 'jac add --npm'")
689
639
  jac_add_result = run(
690
- [*jac_cmd, "add", "--cl"],
640
+ [*jac_cmd, "add", "--npm"],
691
641
  cwd=project_path,
692
642
  capture_output=True,
693
643
  text=True,
694
644
  env=env,
695
645
  )
696
646
  print(
697
- f"[DEBUG] 'jac add --cl' completed returncode={jac_add_result.returncode}\n"
647
+ f"[DEBUG] 'jac add --npm' completed returncode={jac_add_result.returncode}\n"
698
648
  f"STDOUT (truncated):\n{jac_add_result.stdout[:1000]}\n"
699
649
  f"STDERR (truncated):\n{jac_add_result.stderr[:1000]}\n"
700
650
  )
701
651
  if jac_add_result.returncode != 0:
702
652
  pytest.fail(
703
- f"jac add --cl failed\n"
653
+ f"jac add --npm failed\n"
704
654
  f"STDOUT:\n{jac_add_result.stdout}\n"
705
655
  f"STDERR:\n{jac_add_result.stderr}\n"
706
656
  )
707
657
 
708
658
  # 3. Start the server (now uses main.jac at project root)
709
659
  server: Popen[bytes] | None = None
660
+ # Use dynamic port allocation to avoid conflicts when running tests in parallel
661
+ server_port = get_free_port()
710
662
  try:
711
- print("[DEBUG] Starting server with 'jac start main.jac'")
663
+ print(
664
+ f"[DEBUG] Starting server with 'jac start main.jac -p {server_port}'"
665
+ )
712
666
  server = Popen(
713
- [*jac_cmd, "start", "main.jac"],
667
+ [*jac_cmd, "start", "main.jac", "-p", str(server_port)],
714
668
  cwd=project_path,
715
669
  env=env,
716
670
  )
717
671
 
718
672
  # Wait for server to be ready
719
- print("[DEBUG] Waiting for server on 127.0.0.1:8000")
720
- _wait_for_port("127.0.0.1", 8000, timeout=90.0)
721
- print("[DEBUG] Server is accepting connections")
673
+ print(f"[DEBUG] Waiting for server on 127.0.0.1:{server_port}")
674
+ wait_for_port("127.0.0.1", server_port, timeout=90.0)
675
+ print(
676
+ f"[DEBUG] Server is accepting connections on 127.0.0.1:{server_port}"
677
+ )
722
678
 
723
679
  # 4. Test root endpoint - for client-only apps, root serves the HTML app
724
680
  # Note: The root endpoint may return 503 while the client bundle is building.
@@ -726,7 +682,7 @@ def test_default_client_app_renders() -> None:
726
682
  try:
727
683
  print("[DEBUG] Testing root endpoint / (with retry)")
728
684
  root_bytes = _wait_for_endpoint(
729
- "http://127.0.0.1:8000",
685
+ f"http://127.0.0.1:{server_port}",
730
686
  timeout=120.0,
731
687
  poll_interval=2.0,
732
688
  request_timeout=30.0,
@@ -750,7 +706,7 @@ def test_default_client_app_renders() -> None:
750
706
  try:
751
707
  print("[DEBUG] Testing client app endpoint /cl/app")
752
708
  page_bytes = _wait_for_endpoint(
753
- "http://127.0.0.1:8000/cl/app",
709
+ f"http://127.0.0.1:{server_port}/cl/app",
754
710
  timeout=120.0,
755
711
  poll_interval=2.0,
756
712
  request_timeout=30.0,
@@ -786,7 +742,7 @@ def test_default_client_app_renders() -> None:
786
742
  )
787
743
  if script_match:
788
744
  js_path = script_match.group(1)
789
- js_url = f"http://127.0.0.1:8000{js_path}"
745
+ js_url = f"http://127.0.0.1:{server_port}{js_path}"
790
746
  print(f"[DEBUG] Fetching JS bundle from {js_url}")
791
747
  with urlopen(js_url, timeout=30) as resp:
792
748
  js_body = resp.read().decode("utf-8", errors="ignore")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jac-client
3
- Version: 0.2.8
3
+ Version: 0.2.9
4
4
  Summary: Build full-stack web applications with Jac - one language for frontend and backend.
5
5
  Author-email: Jason Mars <jason@mars.ninja>
6
6
  Maintainer-email: Jason Mars <jason@mars.ninja>
@@ -11,7 +11,7 @@ Project-URL: Documentation, https://jac-lang.org
11
11
  Keywords: jac,jaclang,jaseci,frontend,full-stack,web-development
12
12
  Requires-Python: >=3.12
13
13
  Description-Content-Type: text/markdown
14
- Requires-Dist: jaclang==0.9.8
14
+ Requires-Dist: jaclang>=0.9.9
15
15
  Provides-Extra: dev
16
16
  Requires-Dist: python-dotenv==1.0.1; extra == "dev"
17
17
  Requires-Dist: pytest==8.3.5; extra == "dev"
@@ -47,7 +47,7 @@ pip install jac-client
47
47
  ### Create a New App
48
48
 
49
49
  ```bash
50
- jac create --cl my-app
50
+ jac create --use client my-app
51
51
  cd my-app
52
52
  jac start src/app.jac
53
53
  ```
@@ -56,7 +56,7 @@ Visit `http://localhost:8000` to see your app! (The `app` component is served at
56
56
 
57
57
  You can also access the app at `http://localhost:8000/cl/app`.
58
58
 
59
- > **Note**: The `--cl` flag creates a client-side project with an organized folder structure. Without `--cl`, `jac create` creates a standard Jac project.
59
+ > **Note**: The `--npm` flag creates a client-side project with an organized folder structure. Without `--npm`, `jac create` creates a standard Jac project.
60
60
 
61
61
  ---
62
62