jac-client 0.2.3__py3-none-any.whl → 0.2.8__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 (224) hide show
  1. jac_client/examples/all-in-one/app.jac +494 -347
  2. jac_client/examples/all-in-one/assets/workers/worker.py +5 -0
  3. jac_client/examples/all-in-one/button.jac +1 -1
  4. jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
  5. jac_client/examples/all-in-one/components/Header.jac +13 -0
  6. jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
  7. jac_client/examples/all-in-one/components/Summary.jac +53 -0
  8. jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
  9. jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
  10. jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
  11. jac_client/examples/all-in-one/components/button.jac +1 -1
  12. jac_client/examples/all-in-one/components/navigation.jac +132 -0
  13. jac_client/examples/all-in-one/constants/categories.jac +37 -0
  14. jac_client/examples/all-in-one/constants/clients.jac +13 -0
  15. jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
  16. jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
  17. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
  18. jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +70 -0
  19. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +126 -0
  20. jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +552 -0
  21. jac_client/examples/all-in-one/pages/FeaturesTest.jac +126 -0
  22. jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
  23. jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
  24. jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
  25. jac_client/examples/all-in-one/pages/notFound.jac +24 -0
  26. jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
  27. jac_client/examples/all-in-one/utils/formatters.jac +52 -0
  28. jac_client/examples/asset-serving/css-with-image/{app.jac → src/app.jac} +4 -4
  29. jac_client/examples/asset-serving/image-asset/{app.jac → src/app.jac} +4 -4
  30. jac_client/examples/asset-serving/import-alias/{app.jac → src/app.jac} +5 -5
  31. jac_client/examples/basic/{app.jac → src/app.jac} +4 -4
  32. jac_client/examples/basic-auth/src/app.jac +371 -0
  33. jac_client/examples/basic-auth-with-router/{app.jac → src/app.jac} +28 -28
  34. jac_client/examples/basic-full-stack/{app.jac → src/app.jac} +166 -127
  35. jac_client/examples/css-styling/js-styling/{app.jac → src/app.jac} +7 -7
  36. jac_client/examples/css-styling/material-ui/{app.jac → src/app.jac} +6 -6
  37. jac_client/examples/css-styling/pure-css/{app.jac → src/app.jac} +7 -7
  38. jac_client/examples/css-styling/sass-example/{app.jac → src/app.jac} +7 -7
  39. jac_client/examples/css-styling/styled-components/{app.jac → src/app.jac} +6 -6
  40. jac_client/examples/css-styling/tailwind-example/{app.jac → src/app.jac} +7 -7
  41. jac_client/examples/full-stack-with-auth/{app.jac → src/app.jac} +47 -47
  42. jac_client/examples/little-x/{app.jac → src/app.jac} +27 -32
  43. jac_client/examples/little-x/src/submit-button.jac +16 -0
  44. jac_client/examples/nested-folders/nested-advance/{ButtonRoot.jac → src/ButtonRoot.jac} +1 -1
  45. jac_client/examples/nested-folders/nested-advance/{app.jac → src/app.jac} +1 -1
  46. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/ButtonSecondL.jac +1 -1
  47. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/Card.jac +1 -1
  48. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/level2/ButtonThirdL.jac +1 -1
  49. jac_client/examples/nested-folders/nested-basic/{app.jac → src/app.jac} +2 -2
  50. jac_client/examples/nested-folders/nested-basic/{button.jac → src/button.jac} +1 -1
  51. jac_client/examples/nested-folders/nested-basic/{components → src/components}/button.jac +1 -1
  52. jac_client/examples/ts-support/src/app.jac +35 -0
  53. jac_client/examples/with-router/{app.jac → src/app.jac} +15 -15
  54. jac_client/plugin/cli.jac +504 -0
  55. jac_client/plugin/client.jac +45 -0
  56. jac_client/plugin/client_runtime.cl.jac +42 -0
  57. jac_client/plugin/impl/client.impl.jac +193 -0
  58. jac_client/plugin/impl/client_runtime.impl.jac +195 -0
  59. jac_client/plugin/impl/vite_client_bundle.impl.jac +72 -0
  60. jac_client/plugin/plugin_config.jac +195 -0
  61. jac_client/plugin/src/__init__.jac +20 -0
  62. jac_client/plugin/src/asset_processor.jac +33 -0
  63. jac_client/plugin/src/babel_processor.jac +18 -0
  64. jac_client/plugin/src/compiler.jac +67 -0
  65. jac_client/plugin/src/config_loader.jac +32 -0
  66. jac_client/plugin/src/impl/asset_processor.impl.jac +127 -0
  67. jac_client/plugin/src/impl/babel_processor.impl.jac +89 -0
  68. jac_client/plugin/src/impl/compiler.impl.jac +288 -0
  69. jac_client/plugin/src/impl/config_loader.impl.jac +119 -0
  70. jac_client/plugin/src/impl/import_processor.impl.jac +33 -0
  71. jac_client/plugin/src/impl/jac_to_js.impl.jac +41 -0
  72. jac_client/plugin/src/impl/package_installer.impl.jac +105 -0
  73. jac_client/plugin/src/impl/vite_bundler.impl.jac +626 -0
  74. jac_client/plugin/src/import_processor.jac +19 -0
  75. jac_client/plugin/src/jac_to_js.jac +35 -0
  76. jac_client/plugin/src/package_installer.jac +26 -0
  77. jac_client/plugin/src/vite_bundler.jac +44 -0
  78. jac_client/plugin/vite_client_bundle.jac +31 -0
  79. jac_client/tests/conftest.py +283 -0
  80. jac_client/tests/fixtures/basic-app/app.jac +2 -2
  81. jac_client/tests/fixtures/cl_file/app.cl.jac +2 -2
  82. jac_client/tests/fixtures/client_app_with_antd/app.jac +1 -1
  83. jac_client/tests/fixtures/js_import/app.jac +5 -5
  84. jac_client/tests/fixtures/spawn_test/app.jac +15 -18
  85. jac_client/tests/fixtures/with-ts/app.jac +35 -0
  86. jac_client/tests/test_cli.py +811 -0
  87. jac_client/tests/test_it.py +592 -97
  88. {jac_client-0.2.3.dist-info → jac_client-0.2.8.dist-info}/METADATA +41 -34
  89. jac_client-0.2.8.dist-info/RECORD +97 -0
  90. {jac_client-0.2.3.dist-info → jac_client-0.2.8.dist-info}/WHEEL +2 -1
  91. jac_client-0.2.8.dist-info/entry_points.txt +4 -0
  92. jac_client-0.2.8.dist-info/top_level.txt +1 -0
  93. jac_client/docs/README.md +0 -689
  94. jac_client/docs/advanced-state.md +0 -1265
  95. jac_client/docs/asset-serving/intro.md +0 -209
  96. jac_client/docs/assets/pipe_line-v2.svg +0 -32
  97. jac_client/docs/assets/pipe_line.png +0 -0
  98. jac_client/docs/file-system/app.jac.md +0 -121
  99. jac_client/docs/file-system/backend-frontend.md +0 -217
  100. jac_client/docs/file-system/intro.md +0 -72
  101. jac_client/docs/file-system/nested-imports.md +0 -348
  102. jac_client/docs/guide-example/intro.md +0 -115
  103. jac_client/docs/guide-example/step-01-setup.md +0 -270
  104. jac_client/docs/guide-example/step-02-components.md +0 -416
  105. jac_client/docs/guide-example/step-03-styling.md +0 -478
  106. jac_client/docs/guide-example/step-04-todo-ui.md +0 -477
  107. jac_client/docs/guide-example/step-05-local-state.md +0 -530
  108. jac_client/docs/guide-example/step-06-events.md +0 -749
  109. jac_client/docs/guide-example/step-07-effects.md +0 -468
  110. jac_client/docs/guide-example/step-08-walkers.md +0 -534
  111. jac_client/docs/guide-example/step-09-authentication.md +0 -586
  112. jac_client/docs/guide-example/step-10-routing.md +0 -539
  113. jac_client/docs/guide-example/step-11-final.md +0 -963
  114. jac_client/docs/imports.md +0 -1141
  115. jac_client/docs/lifecycle-hooks.md +0 -773
  116. jac_client/docs/routing.md +0 -659
  117. jac_client/docs/styling/intro.md +0 -249
  118. jac_client/docs/styling/js-styling.md +0 -367
  119. jac_client/docs/styling/material-ui.md +0 -341
  120. jac_client/docs/styling/pure-css.md +0 -299
  121. jac_client/docs/styling/sass.md +0 -403
  122. jac_client/docs/styling/styled-components.md +0 -395
  123. jac_client/docs/styling/tailwind.md +0 -298
  124. jac_client/examples/all-in-one/.babelrc +0 -9
  125. jac_client/examples/all-in-one/README.md +0 -16
  126. jac_client/examples/all-in-one/assets/burger.png +0 -0
  127. jac_client/examples/all-in-one/package.json +0 -29
  128. jac_client/examples/all-in-one/styles.css +0 -26
  129. jac_client/examples/all-in-one/vite.config.js +0 -28
  130. jac_client/examples/asset-serving/css-with-image/.babelrc +0 -9
  131. jac_client/examples/asset-serving/css-with-image/README.md +0 -91
  132. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  133. jac_client/examples/asset-serving/css-with-image/package.json +0 -28
  134. jac_client/examples/asset-serving/css-with-image/styles.css +0 -26
  135. jac_client/examples/asset-serving/css-with-image/vite.config.js +0 -28
  136. jac_client/examples/asset-serving/image-asset/.babelrc +0 -9
  137. jac_client/examples/asset-serving/image-asset/README.md +0 -119
  138. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  139. jac_client/examples/asset-serving/image-asset/package.json +0 -28
  140. jac_client/examples/asset-serving/image-asset/styles.css +0 -26
  141. jac_client/examples/asset-serving/image-asset/vite.config.js +0 -28
  142. jac_client/examples/asset-serving/import-alias/.babelrc +0 -9
  143. jac_client/examples/asset-serving/import-alias/README.md +0 -83
  144. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  145. jac_client/examples/asset-serving/import-alias/package.json +0 -28
  146. jac_client/examples/asset-serving/import-alias/vite.config.js +0 -28
  147. jac_client/examples/basic/.babelrc +0 -9
  148. jac_client/examples/basic/README.md +0 -16
  149. jac_client/examples/basic/package.json +0 -27
  150. jac_client/examples/basic/vite.config.js +0 -27
  151. jac_client/examples/basic-auth/.babelrc +0 -9
  152. jac_client/examples/basic-auth/README.md +0 -16
  153. jac_client/examples/basic-auth/app.jac +0 -308
  154. jac_client/examples/basic-auth/package.json +0 -27
  155. jac_client/examples/basic-auth/vite.config.js +0 -27
  156. jac_client/examples/basic-auth-with-router/.babelrc +0 -9
  157. jac_client/examples/basic-auth-with-router/README.md +0 -60
  158. jac_client/examples/basic-auth-with-router/package.json +0 -28
  159. jac_client/examples/basic-auth-with-router/vite.config.js +0 -27
  160. jac_client/examples/basic-full-stack/.babelrc +0 -9
  161. jac_client/examples/basic-full-stack/README.md +0 -18
  162. jac_client/examples/basic-full-stack/package.json +0 -28
  163. jac_client/examples/basic-full-stack/vite.config.js +0 -27
  164. jac_client/examples/css-styling/js-styling/.babelrc +0 -9
  165. jac_client/examples/css-styling/js-styling/README.md +0 -183
  166. jac_client/examples/css-styling/js-styling/package.json +0 -28
  167. jac_client/examples/css-styling/js-styling/styles.js +0 -100
  168. jac_client/examples/css-styling/js-styling/vite.config.js +0 -27
  169. jac_client/examples/css-styling/material-ui/.babelrc +0 -9
  170. jac_client/examples/css-styling/material-ui/README.md +0 -16
  171. jac_client/examples/css-styling/material-ui/package.json +0 -32
  172. jac_client/examples/css-styling/material-ui/vite.config.js +0 -27
  173. jac_client/examples/css-styling/pure-css/.babelrc +0 -9
  174. jac_client/examples/css-styling/pure-css/README.md +0 -16
  175. jac_client/examples/css-styling/pure-css/package.json +0 -28
  176. jac_client/examples/css-styling/pure-css/styles.css +0 -111
  177. jac_client/examples/css-styling/pure-css/vite.config.js +0 -27
  178. jac_client/examples/css-styling/sass-example/.babelrc +0 -9
  179. jac_client/examples/css-styling/sass-example/README.md +0 -16
  180. jac_client/examples/css-styling/sass-example/package.json +0 -29
  181. jac_client/examples/css-styling/sass-example/styles.scss +0 -153
  182. jac_client/examples/css-styling/sass-example/vite.config.js +0 -27
  183. jac_client/examples/css-styling/styled-components/.babelrc +0 -9
  184. jac_client/examples/css-styling/styled-components/README.md +0 -16
  185. jac_client/examples/css-styling/styled-components/package.json +0 -29
  186. jac_client/examples/css-styling/styled-components/styled.js +0 -90
  187. jac_client/examples/css-styling/styled-components/vite.config.js +0 -27
  188. jac_client/examples/css-styling/tailwind-example/.babelrc +0 -9
  189. jac_client/examples/css-styling/tailwind-example/README.md +0 -16
  190. jac_client/examples/css-styling/tailwind-example/global.css +0 -1
  191. jac_client/examples/css-styling/tailwind-example/package.json +0 -30
  192. jac_client/examples/css-styling/tailwind-example/vite.config.js +0 -29
  193. jac_client/examples/full-stack-with-auth/.babelrc +0 -9
  194. jac_client/examples/full-stack-with-auth/README.md +0 -16
  195. jac_client/examples/full-stack-with-auth/package.json +0 -28
  196. jac_client/examples/full-stack-with-auth/vite.config.js +0 -29
  197. jac_client/examples/little-x/package.json +0 -23
  198. jac_client/examples/little-x/submit-button.jac +0 -8
  199. jac_client/examples/nested-folders/nested-advance/.babelrc +0 -9
  200. jac_client/examples/nested-folders/nested-advance/README.md +0 -77
  201. jac_client/examples/nested-folders/nested-advance/package.json +0 -29
  202. jac_client/examples/nested-folders/nested-advance/vite.config.js +0 -28
  203. jac_client/examples/nested-folders/nested-basic/.babelrc +0 -9
  204. jac_client/examples/nested-folders/nested-basic/README.md +0 -183
  205. jac_client/examples/nested-folders/nested-basic/app.js +0 -7
  206. jac_client/examples/nested-folders/nested-basic/package.json +0 -28
  207. jac_client/examples/nested-folders/nested-basic/vite.config.js +0 -27
  208. jac_client/examples/with-router/.babelrc +0 -9
  209. jac_client/examples/with-router/README.md +0 -17
  210. jac_client/examples/with-router/package.json +0 -28
  211. jac_client/examples/with-router/vite.config.js +0 -27
  212. jac_client/plugin/cli.py +0 -244
  213. jac_client/plugin/client.py +0 -152
  214. jac_client/plugin/client_runtime.jac +0 -234
  215. jac_client/plugin/vite_client_bundle.py +0 -503
  216. jac_client/tests/fixtures/js_import/utils.js +0 -21
  217. jac_client/tests/fixtures/package-lock.json +0 -329
  218. jac_client/tests/fixtures/package.json +0 -11
  219. jac_client/tests/test_asset_examples.py +0 -322
  220. jac_client/tests/test_cl.py +0 -530
  221. jac_client/tests/test_create_jac_app.py +0 -131
  222. jac_client/tests/test_nested_file.py +0 -374
  223. jac_client-0.2.3.dist-info/RECORD +0 -171
  224. jac_client-0.2.3.dist-info/entry_points.txt +0 -4
@@ -0,0 +1,26 @@
1
+ """Package installer for managing npm dependencies in jac.toml."""
2
+
3
+ import shutil;
4
+ import subprocess;
5
+ import from pathlib { Path }
6
+ import from jaclang.runtimelib.client_bundle { ClientBundleError }
7
+ import from .config_loader { JacClientConfig }
8
+ import from .vite_bundler { ViteBundler }
9
+
10
+ class PackageInstaller {
11
+ def init(self: PackageInstaller, project_dir: Path);
12
+ def install_package(
13
+ self: PackageInstaller,
14
+ package_name: str,
15
+ version: (str | None) = None,
16
+ is_dev: bool = False
17
+ ) -> None;
18
+
19
+ def install_all(self: PackageInstaller) -> None;
20
+ def _regenerate_and_install(self: PackageInstaller) -> None;
21
+ def uninstall_package(
22
+ self: PackageInstaller, package_name: str, is_dev: bool = False
23
+ ) -> None;
24
+
25
+ def list_packages(self: PackageInstaller) -> dict[str, dict[(str, str)]];
26
+ }
@@ -0,0 +1,44 @@
1
+ """Vite bundling module."""
2
+ import hashlib;
3
+ import json;
4
+ import shutil;
5
+ import subprocess;
6
+ import from pathlib { Path }
7
+ import from typing { Any, Optional }
8
+ import from jaclang.runtimelib.client_bundle { ClientBundleError }
9
+ import from .config_loader { JacClientConfig }
10
+ """Handles Vite bundling operations."""
11
+ class ViteBundler {
12
+ def init(
13
+ self: ViteBundler,
14
+ project_dir: Path,
15
+ output_dir: Optional[Path] = None,
16
+ minify: bool = False,
17
+ config_path: Optional[Path] = None
18
+ );
19
+
20
+ def _get_client_dir(self: ViteBundler) -> Path;
21
+ def build(self: ViteBundler, entry_file: Optional[Path] = None) -> None;
22
+ def find_bundle(self: ViteBundler) -> Optional[Path];
23
+ def find_css(self: ViteBundler) -> Optional[Path];
24
+ def read_bundle(self: ViteBundler) -> tuple[str, str];
25
+ def _has_typescript_support(self: ViteBundler) -> bool;
26
+ def create_vite_config(self: ViteBundler, entry_file: Path) -> Path;
27
+ def _get_plugin_var_name(self: ViteBundler, plugin_name: str) -> str;
28
+ def _format_plugin_options(self: ViteBundler, options: dict) -> str;
29
+ def _format_config_object(self: ViteBundler, config: dict, indent: int = 0) -> str;
30
+ def _ensure_root_package_json(self: ViteBundler) -> None;
31
+ def _cleanup_root_package_files(self: ViteBundler) -> None;
32
+ def create_package_json(
33
+ self: ViteBundler, project_name: Optional[str] = None
34
+ ) -> Path;
35
+
36
+ def create_tsconfig(self: ViteBundler) -> Path;
37
+ """Create a dev-mode vite config with API proxy for HMR."""
38
+ def create_dev_vite_config(
39
+ self: ViteBundler, entry_file: Path, api_port: int = 8000
40
+ ) -> Path;
41
+
42
+ """Start Vite dev server as a subprocess."""
43
+ def start_dev_server(self: ViteBundler, port: int = 3000) -> Any;
44
+ }
@@ -0,0 +1,31 @@
1
+ """Vite-enhanced client bundle generation for Jac web front-ends."""
2
+ import contextlib;
3
+ import shutil;
4
+ import from collections.abc { Callable }
5
+ import from pathlib { Path }
6
+ import from types { ModuleType }
7
+ import from typing { cast }
8
+ import from jaclang.runtimelib.client_bundle {
9
+ ClientBundle,
10
+ ClientBundleBuilder,
11
+ ClientBundleError
12
+ }
13
+ import from .src { ViteCompiler }
14
+
15
+ """Enhanced ClientBundleBuilder that uses Vite for optimized bundling."""
16
+ class ViteClientBundleBuilder(ClientBundleBuilder) {
17
+ def init(
18
+ self: ViteClientBundleBuilder,
19
+ runtime_path: (Path | None) = None,
20
+ vite_output_dir: (Path | None) = None,
21
+ vite_package_json: (Path | None) = None,
22
+ vite_minify: bool = False
23
+ ) -> None;
24
+
25
+ def _get_compiler(self: ViteClientBundleBuilder) -> ViteCompiler;
26
+ def _compile_bundle(
27
+ self: ViteClientBundleBuilder, module: ModuleType, module_path: Path
28
+ ) -> ClientBundle;
29
+
30
+ def cleanup_temp_dir(self: ViteClientBundleBuilder) -> None;
31
+ }
@@ -0,0 +1,283 @@
1
+ """Pytest configuration and shared fixtures for jac-client tests.
2
+
3
+ This module provides session-scoped fixtures to optimize test execution by:
4
+ 1. Running npm install once per session and caching node_modules
5
+ 2. Providing shared Vite build infrastructure
6
+ 3. Mocking npm install for tests that only need jac.toml manipulation
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import contextlib
12
+ import os
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+ import tempfile
17
+ from collections.abc import Generator
18
+ from pathlib import Path
19
+ from unittest.mock import patch
20
+
21
+ import pytest
22
+
23
+ from jaclang.pycore.runtime import JacRuntime as Jac
24
+ from jaclang.pycore.runtime import JacRuntimeImpl, plugin_manager
25
+
26
+ # Store unregistered plugins globally for session-level management
27
+ _external_plugins: list = []
28
+
29
+
30
+ def pytest_configure(config: pytest.Config) -> None:
31
+ """Disable jac-scale plugin at the start of the test session.
32
+
33
+ jac-scale plugin is disabled during tests to avoid MongoDB connections
34
+ and other jac-scale specific dependencies. jac-client plugin is kept
35
+ enabled since we're testing it.
36
+ """
37
+ global _external_plugins
38
+ for name, plugin in list(plugin_manager.list_name_plugin()):
39
+ # Keep core runtime and jac-client plugins
40
+ if plugin is JacRuntimeImpl or name == "JacRuntimeImpl":
41
+ continue
42
+ if "client" in name.lower() or "JacClient" in str(type(plugin)):
43
+ continue
44
+ # Disable jac-scale and other external plugins
45
+ _external_plugins.append((name, plugin))
46
+ plugin_manager.unregister(plugin=plugin, name=name)
47
+
48
+
49
+ def pytest_unconfigure(config: pytest.Config) -> None:
50
+ """Re-register external plugins at the end of the test session."""
51
+ global _external_plugins
52
+ for name, plugin in _external_plugins:
53
+ with contextlib.suppress(ValueError):
54
+ plugin_manager.register(plugin, name=name)
55
+ _external_plugins.clear()
56
+
57
+
58
+ def _get_jac_command() -> list[str]:
59
+ """Get the jac command with proper path handling."""
60
+ # Try to find jac in PATH or use python -m jaclang
61
+ jac_path = shutil.which("jac")
62
+ if jac_path:
63
+ return [jac_path]
64
+ # Fall back to running via python module
65
+ return [sys.executable, "-m", "jaclang"]
66
+
67
+
68
+ def _get_env_with_npm() -> dict[str, str]:
69
+ """Get environment dict with npm in PATH."""
70
+ env = os.environ.copy()
71
+ # npm might be installed via nvm, ensure PATH includes common locations
72
+ npm_path = shutil.which("npm")
73
+ if npm_path:
74
+ npm_dir = str(Path(npm_path).parent)
75
+ current_path = env.get("PATH", "")
76
+ if npm_dir not in current_path:
77
+ env["PATH"] = f"{npm_dir}:{current_path}"
78
+ return env
79
+
80
+
81
+ @pytest.fixture(autouse=True)
82
+ def reset_jac_machine() -> Generator[None, None, None]:
83
+ """Reset Jac machine before and after each test."""
84
+ Jac.reset_machine()
85
+ yield
86
+ Jac.reset_machine()
87
+
88
+
89
+ # Session-scoped cache for npm installation
90
+ _npm_cache_dir: Path | None = None
91
+
92
+
93
+ def _get_minimal_jac_toml() -> str:
94
+ """Get minimal jac.toml content for npm cache setup."""
95
+ return """[project]
96
+ name = "npm-cache"
97
+ version = "0.0.1"
98
+ description = "Cached npm modules"
99
+ entry-point = "app.jac"
100
+
101
+ [plugins.client.vite.build]
102
+ minify = false
103
+ """
104
+
105
+
106
+ @pytest.fixture(scope="session")
107
+ def npm_cache_dir() -> Generator[Path, None, None]:
108
+ """Session-scoped fixture that provides a directory with npm packages installed.
109
+
110
+ This runs npm install once per test session and provides the path to the
111
+ .jac/client/configs directory containing node_modules.
112
+ """
113
+ global _npm_cache_dir
114
+
115
+ if _npm_cache_dir is not None and _npm_cache_dir.exists():
116
+ yield _npm_cache_dir
117
+ return
118
+
119
+ # Create a persistent temp directory for the session
120
+ cache_dir = Path(tempfile.mkdtemp(prefix="jac_npm_cache_"))
121
+
122
+ # Create jac.toml
123
+ jac_toml = cache_dir / "jac.toml"
124
+ jac_toml.write_text(_get_minimal_jac_toml())
125
+
126
+ # Run jac add --cl to install packages
127
+ jac_cmd = _get_jac_command()
128
+ env = _get_env_with_npm()
129
+ result = subprocess.run(
130
+ [*jac_cmd, "add", "--cl"],
131
+ cwd=cache_dir,
132
+ capture_output=True,
133
+ text=True,
134
+ env=env,
135
+ )
136
+
137
+ if result.returncode != 0:
138
+ # Clean up on failure
139
+ shutil.rmtree(cache_dir, ignore_errors=True)
140
+ pytest.skip(f"Failed to set up npm cache: {result.stderr}")
141
+
142
+ _npm_cache_dir = cache_dir
143
+ yield cache_dir
144
+
145
+ # Cleanup after all tests complete
146
+ shutil.rmtree(cache_dir, ignore_errors=True)
147
+
148
+
149
+ @pytest.fixture
150
+ def vite_project_dir(npm_cache_dir: Path, tmp_path: Path) -> Path:
151
+ """Fixture that provides a project directory with pre-installed node_modules.
152
+
153
+ This copies node_modules from the session cache instead of running npm install.
154
+ """
155
+ # Create jac.toml in the temp directory
156
+ jac_toml = tmp_path / "jac.toml"
157
+ jac_toml.write_text(_get_minimal_jac_toml())
158
+
159
+ # Copy .jac/client/configs directory (contains package.json)
160
+ source_configs = npm_cache_dir / ".jac" / "client" / "configs"
161
+ dest_configs = tmp_path / ".jac" / "client" / "configs"
162
+ if source_configs.exists():
163
+ dest_configs.parent.mkdir(parents=True, exist_ok=True)
164
+ shutil.copytree(source_configs, dest_configs, symlinks=True)
165
+
166
+ # Copy node_modules from project root (npm installs there, not in .jac/client/configs)
167
+ source_node_modules = npm_cache_dir / "node_modules"
168
+ dest_node_modules = tmp_path / "node_modules"
169
+ if source_node_modules.exists():
170
+ shutil.copytree(source_node_modules, dest_node_modules, symlinks=True)
171
+
172
+ # Create required directories
173
+ (tmp_path / "dist").mkdir(exist_ok=True)
174
+ (tmp_path / "compiled").mkdir(exist_ok=True)
175
+ (tmp_path / "build").mkdir(exist_ok=True)
176
+
177
+ return tmp_path
178
+
179
+
180
+ @pytest.fixture
181
+ def vite_project_with_antd(npm_cache_dir: Path, tmp_path: Path) -> Path:
182
+ """Fixture that provides a project directory with antd pre-installed."""
183
+ # Create jac.toml with antd dependency
184
+ jac_toml_content = """[project]
185
+ name = "antd-test"
186
+ version = "0.0.1"
187
+ description = "Test project with antd"
188
+ entry-point = "app.jac"
189
+
190
+ [plugins.client.vite.build]
191
+ minify = false
192
+
193
+ [dependencies.npm]
194
+ antd = "^6.0.0"
195
+ """
196
+ jac_toml = tmp_path / "jac.toml"
197
+ jac_toml.write_text(jac_toml_content)
198
+
199
+ # Copy base .jac/client/configs first for faster install
200
+ source_configs = npm_cache_dir / ".jac" / "client" / "configs"
201
+ dest_configs = tmp_path / ".jac" / "client" / "configs"
202
+ if source_configs.exists():
203
+ dest_configs.parent.mkdir(parents=True, exist_ok=True)
204
+ shutil.copytree(source_configs, dest_configs, symlinks=True)
205
+
206
+ # Copy base node_modules for faster install (npm will add antd on top)
207
+ source_node_modules = npm_cache_dir / "node_modules"
208
+ dest_node_modules = tmp_path / "node_modules"
209
+ if source_node_modules.exists():
210
+ shutil.copytree(source_node_modules, dest_node_modules, symlinks=True)
211
+
212
+ # Install antd on top (uses cached node_modules as base)
213
+ jac_cmd = _get_jac_command()
214
+ env = _get_env_with_npm()
215
+ result = subprocess.run(
216
+ [*jac_cmd, "add", "--cl"],
217
+ cwd=tmp_path,
218
+ capture_output=True,
219
+ text=True,
220
+ env=env,
221
+ )
222
+ if result.returncode != 0:
223
+ pytest.skip(f"Failed to install antd: {result.stderr}")
224
+
225
+ # Create required directories
226
+ (tmp_path / "dist").mkdir(exist_ok=True)
227
+ (tmp_path / "compiled").mkdir(exist_ok=True)
228
+ (tmp_path / "build").mkdir(exist_ok=True)
229
+
230
+ return tmp_path
231
+
232
+
233
+ @pytest.fixture
234
+ def mock_npm_install():
235
+ """Fixture that mocks npm install for tests that only test jac.toml manipulation.
236
+
237
+ Use this for CLI tests (add/remove commands) that don't need actual npm packages.
238
+ """
239
+ with patch(
240
+ "jac_client.plugin.src.package_installer.PackageInstaller._regenerate_and_install"
241
+ ) as mock:
242
+ yield mock
243
+
244
+
245
+ @pytest.fixture
246
+ def cli_test_dir(tmp_path: Path) -> Path:
247
+ """Fixture that provides a minimal test directory for CLI tests."""
248
+ return tmp_path
249
+
250
+
251
+ def create_test_jac_toml(
252
+ path: Path,
253
+ deps: str = "",
254
+ dev_deps: str = "",
255
+ name: str = "test-project",
256
+ ) -> Path:
257
+ """Helper to create a jac.toml file for testing.
258
+
259
+ Args:
260
+ path: Directory to create jac.toml in
261
+ deps: Dependencies to add (TOML format, e.g., 'lodash = "^4.17.21"')
262
+ dev_deps: Dev dependencies to add (TOML format)
263
+ name: Project name
264
+
265
+ Returns:
266
+ Path to the created jac.toml file
267
+ """
268
+ deps_section = f"\n{deps}" if deps else ""
269
+ dev_deps_section = f"\n{dev_deps}" if dev_deps else ""
270
+
271
+ content = f"""[project]
272
+ name = "{name}"
273
+ version = "1.0.0"
274
+ description = "Test project"
275
+ entry-point = "app.jac"
276
+
277
+ [dependencies.npm]{deps_section}
278
+
279
+ [dev-dependencies.npm]{dev_deps_section}
280
+ """
281
+ config_path = path / "jac.toml"
282
+ config_path.write_text(content)
283
+ return config_path
@@ -1,6 +1,6 @@
1
1
  """Sample Jac module containing client-side declarations."""
2
2
 
3
- cl let API_LABEL: str = "Runtime Test";
3
+ cl glob API_LABEL: str = "Runtime Test";
4
4
 
5
5
  cl obj ButtonProps {
6
6
  has label: str = "Tap Me";
@@ -8,7 +8,7 @@ cl obj ButtonProps {
8
8
  }
9
9
 
10
10
  cl def app() {
11
- let props = ButtonProps(label="Tap Me", color="primary");
11
+ props = ButtonProps(label="Tap Me", color="primary");
12
12
  return <div class="app">
13
13
  <h1>
14
14
  {API_LABEL}
@@ -1,8 +1,8 @@
1
1
  import from react { useState }
2
2
 
3
3
  def app() -> any {
4
- let [todos, setTodos] = useState([]);
5
- let [input, setInput] = useState("");
4
+ [todos, setTodos] = useState([]);
5
+ [input, setInput] = useState("");
6
6
 
7
7
  # Event Handler
8
8
  async def addTodo() -> None {
@@ -2,7 +2,7 @@
2
2
 
3
3
  cl import from antd { Button }
4
4
 
5
- cl let APP_NAME: str = "Ant Design Test";
5
+ cl glob APP_NAME: str = "Ant Design Test";
6
6
 
7
7
  cl def ButtonTest() {
8
8
  return <div>
@@ -2,13 +2,13 @@
2
2
 
3
3
  cl import from .utils { formatMessage, calculateSum, JS_CONSTANT, MessageFormatter }
4
4
 
5
- cl let JS_IMPORT_LABEL: str = "JavaScript Import Test";
5
+ cl glob JS_IMPORT_LABEL: str = "JavaScript Import Test";
6
6
 
7
7
  cl def JsImportTest() -> any {
8
- let greeting = formatMessage("Jac");
9
- let sum = calculateSum(5, 3);
10
- let formatter = MessageFormatter("JS");
11
- let formatted = formatter.format("Hello from JS class");
8
+ greeting = formatMessage("Jac");
9
+ sum = calculateSum(5, 3);
10
+ formatter = MessageFormatter("JS");
11
+ formatted = formatter.format("Hello from JS class");
12
12
 
13
13
  return <div class="js-import-test">
14
14
  <h1>
@@ -32,51 +32,48 @@ walker positional_walker {
32
32
  }
33
33
 
34
34
  # Client-side code testing both spawn orderings
35
- cl import from react {
36
- useState,
37
- useEffect
38
- }
35
+ cl import from react { useEffect }
39
36
 
40
37
  cl {
41
38
  def app() -> any {
42
- let [standardResult, setStandardResult] = useState(None);
43
- let [standardComputed, setStandardComputed] = useState(None);
44
- let [reverseResult, setReverseResult] = useState(None);
45
- let [uuidResult, setUuidResult] = useState(None);
46
- let [reverseUuidResult, setReverseUuidResult] = useState(None);
47
- let [positionalResult, setPositionalResult] = useState(None);
48
- let [spreadResult, setSpreadResult] = useState(None);
39
+ has standardResult: any = None;
40
+ has standardComputed: any = None;
41
+ has reverseResult: any = None;
42
+ has uuidResult: any = None;
43
+ has reverseUuidResult: any = None;
44
+ has positionalResult: any = None;
45
+ has spreadResult: any = None;
49
46
 
50
47
  async def loadData() -> None {
51
48
  # Test standard spawn order: node spawn walker()
52
49
  data1 = root spawn test_walker();
53
- setStandardResult(data1);
50
+ standardResult = data1;
54
51
 
55
52
  data2 = root spawn parameterized_walker(value=42);
56
- setStandardComputed(data2);
53
+ standardComputed = data2;
57
54
 
58
55
  # Test reverse spawn order: walker() spawn node
59
56
  data3 = test_walker(message="Reverse spawn!") spawn root;
60
- setReverseResult(data3);
57
+ reverseResult = data3;
61
58
 
62
59
  # Test spawn with UUID string: uuid_string spawn walker()
63
60
  node_id = "550e8400-e29b-41d4-a716-446655440000";
64
61
  data4 = node_id spawn test_walker();
65
- setUuidResult(data4);
62
+ uuidResult = data4;
66
63
 
67
64
  # Test reverse spawn with UUID string: walker() spawn uuid_string
68
65
  another_node_id = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
69
66
  data5 = parameterized_walker(value=100) spawn another_node_id;
70
- setReverseUuidResult(data5);
67
+ reverseUuidResult = data5;
71
68
 
72
69
  # Test positional walker arguments inferred from has fields
73
70
  data6 = node_id spawn positional_walker("Node positional", 2);
74
- setPositionalResult(data6);
71
+ positionalResult = data6;
75
72
 
76
73
  # Test **kwargs via spread when walker is on left-hand side
77
74
  extra_fields = {"metadata": {"source": "client-side"}};
78
75
  data7 = positional_walker("Spread order", 5, **extra_fields) spawn root;
79
- setSpreadResult(data7);
76
+ spreadResult = data7;
80
77
  }
81
78
 
82
79
  useEffect(lambda -> None{ loadData();} , []);
@@ -0,0 +1,35 @@
1
+
2
+ # Pages
3
+ cl import from react { useEffect }
4
+ cl import from ".components/Button.tsx" { Button }
5
+
6
+ cl {
7
+ def app() -> any {
8
+ has count: int = 0;
9
+ useEffect(lambda -> None{ console.log("Count: ", count);} , [count]);
10
+ return <div
11
+ style={{padding: "2rem", fontFamily: "Arial, sans-serif"}}
12
+ >
13
+ <h1>
14
+ Hello, World!
15
+ </h1>
16
+ <p>
17
+ Count: {count}
18
+ </p>
19
+ <div
20
+ style={{display: "flex", gap: "1rem", marginTop: "1rem"}}
21
+ >
22
+ <Button
23
+ label="Increment"
24
+ onClick={lambda -> None{ count = count + 1;} }
25
+ variant="primary"
26
+ />
27
+ <Button
28
+ label="Reset"
29
+ onClick={lambda -> None{ count = 0;} }
30
+ variant="secondary"
31
+ />
32
+ </div>
33
+ </div>;
34
+ }
35
+ }