jac-client 0.2.6__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 (66) hide show
  1. jac_client/examples/all-in-one/app.jac +573 -0
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
  3. jac_client/examples/all-in-one/components/Header.jac +13 -0
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
  5. jac_client/examples/all-in-one/components/Summary.jac +53 -0
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
  8. jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
  9. jac_client/examples/all-in-one/components/navigation.jac +132 -0
  10. jac_client/examples/all-in-one/constants/categories.jac +37 -0
  11. jac_client/examples/all-in-one/constants/clients.jac +13 -0
  12. jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
  13. jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
  14. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
  15. jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +70 -0
  16. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +126 -0
  17. jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +552 -0
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +126 -0
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
  20. jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
  21. jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
  22. jac_client/examples/all-in-one/pages/notFound.jac +24 -0
  23. jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
  24. jac_client/examples/all-in-one/utils/formatters.jac +52 -0
  25. jac_client/examples/asset-serving/css-with-image/src/app.jac +3 -3
  26. jac_client/examples/asset-serving/image-asset/src/app.jac +3 -3
  27. jac_client/examples/asset-serving/import-alias/src/app.jac +3 -3
  28. jac_client/examples/basic/src/app.jac +3 -3
  29. jac_client/examples/basic-auth/src/app.jac +31 -37
  30. jac_client/examples/basic-auth-with-router/src/app.jac +16 -16
  31. jac_client/examples/basic-full-stack/src/app.jac +24 -30
  32. jac_client/examples/css-styling/js-styling/src/app.jac +5 -5
  33. jac_client/examples/css-styling/material-ui/src/app.jac +5 -5
  34. jac_client/examples/css-styling/pure-css/src/app.jac +5 -5
  35. jac_client/examples/css-styling/sass-example/src/app.jac +5 -5
  36. jac_client/examples/css-styling/styled-components/src/app.jac +5 -5
  37. jac_client/examples/css-styling/tailwind-example/src/app.jac +5 -5
  38. jac_client/examples/full-stack-with-auth/src/app.jac +16 -16
  39. jac_client/examples/ts-support/src/app.jac +4 -4
  40. jac_client/examples/with-router/src/app.jac +4 -4
  41. jac_client/plugin/cli.jac +160 -203
  42. jac_client/plugin/client.jac +8 -15
  43. jac_client/plugin/client_runtime.cl.jac +18 -14
  44. jac_client/plugin/impl/client.impl.jac +85 -26
  45. jac_client/plugin/impl/client_runtime.impl.jac +27 -9
  46. jac_client/plugin/plugin_config.jac +11 -11
  47. jac_client/plugin/src/compiler.jac +2 -1
  48. jac_client/plugin/src/impl/babel_processor.impl.jac +22 -17
  49. jac_client/plugin/src/impl/compiler.impl.jac +55 -18
  50. jac_client/plugin/src/impl/vite_bundler.impl.jac +215 -102
  51. jac_client/plugin/src/package_installer.jac +1 -1
  52. jac_client/plugin/src/vite_bundler.jac +9 -1
  53. jac_client/tests/conftest.py +10 -8
  54. jac_client/tests/fixtures/spawn_test/app.jac +15 -18
  55. jac_client/tests/fixtures/with-ts/app.jac +4 -4
  56. jac_client/tests/test_cli.py +105 -49
  57. jac_client/tests/test_it.py +297 -82
  58. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/METADATA +16 -7
  59. jac_client-0.2.8.dist-info/RECORD +97 -0
  60. jac_client/examples/all-in-one/src/app.jac +0 -841
  61. jac_client-0.2.6.dist-info/RECORD +0 -74
  62. /jac_client/examples/all-in-one/{src/button.jac → button.jac} +0 -0
  63. /jac_client/examples/all-in-one/{src/components → components}/button.jac +0 -0
  64. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/WHEEL +0 -0
  65. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/entry_points.txt +0 -0
  66. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/top_level.txt +0 -0
@@ -30,13 +30,6 @@ impl JacClient.send_static_file(
30
30
  }
31
31
  }
32
32
 
33
- """Get a module introspector for the supplied module."""
34
- impl JacClient.get_module_introspector(
35
- module_name: str, base_path: (str | None)
36
- ) -> ModuleIntrospector {
37
- return JacClientModuleIntrospector(module_name, base_path);
38
- }
39
-
40
33
  """Build a client bundle for the supplied module."""
41
34
  impl JacClient.build_client_bundle(
42
35
  module: types.ModuleType, force: bool = False
@@ -57,12 +50,13 @@ impl JacClient.get_client_bundle_builder -> ViteClientBundleBuilder {
57
50
  # Fallback to base_path_dir if no project root found
58
51
  base_path = base_path_dir;
59
52
  }
60
- # package.json should only exist in .client-build/.jac-client.configs/
61
- generated_package_json = base_path / '.client-build' / '.jac-client.configs' / 'package.json';
53
+ # Use ViteBundler to get the client directory
54
+ bundler = ViteBundler(base_path);
55
+ client_dir = bundler._get_client_dir();
56
+ # package.json should only exist in .jac/client/configs/
57
+ generated_package_json = client_dir / 'configs' / 'package.json';
62
58
  # Generate package.json if it doesn't exist
63
59
  if not generated_package_json.exists() {
64
- # Use ViteBundler to generate package.json from config.json
65
- bundler = ViteBundler(base_path);
66
60
  generated_path = bundler.create_package_json();
67
61
  # Verify the file was created and resolve to absolute path
68
62
  if not generated_path.exists() {
@@ -80,7 +74,7 @@ impl JacClient.get_client_bundle_builder -> ViteClientBundleBuilder {
80
74
  f'package.json not found at {package_json_path}. Expected at {generated_package_json}'
81
75
  ) ;
82
76
  }
83
- output_dir = base_path / '.client-build' / 'dist';
77
+ output_dir = client_dir / 'dist';
84
78
  runtime_path = Path(__file__).with_name('client_runtime.cl.jac');
85
79
  return ViteClientBundleBuilder(
86
80
  runtime_path=runtime_path,
@@ -91,22 +85,22 @@ impl JacClient.get_client_bundle_builder -> ViteClientBundleBuilder {
91
85
  }
92
86
 
93
87
  """Render HTML page for client function using the Vite bundle."""
94
- impl JacClientModuleIntrospector.render_page(
95
- self: JacClientModuleIntrospector,
88
+ impl JacClient.render_page(
89
+ introspector: ModuleIntrospector,
96
90
  function_name: str,
97
91
  args: dict[(str, Any)],
98
92
  username: str
99
93
  ) -> dict[str, Any] {
100
- self.load();
94
+ introspector.load();
101
95
  available_exports = (
102
- set(self._client_manifest.get('exports', []))
103
- or set(self.get_client_functions().keys())
96
+ set(introspector._client_manifest.get('exports', []))
97
+ or set(introspector.get_client_functions().keys())
104
98
  );
105
99
  if (function_name not in available_exports) {
106
100
  raise ValueError(f"Client function '{function_name}' not found") ;
107
101
  }
108
- bundle_hash = self.ensure_bundle();
109
- import from jaclang.project.config { find_project_root }
102
+ bundle_hash = introspector.ensure_bundle();
103
+ import from jaclang.project.config { find_project_root, get_config }
110
104
  # Find project root by looking for jac.toml (base_path_dir might be src/ for entry files)
111
105
  base_path_dir = Path(Jac.base_path_dir);
112
106
  project_root_result = find_project_root(base_path_dir);
@@ -116,19 +110,84 @@ impl JacClientModuleIntrospector.render_page(
116
110
  # Fallback to base_path_dir if no project root found
117
111
  base_path = base_path_dir;
118
112
  }
119
- dist_dir = base_path / '.client-build' / 'dist';
113
+ # Get client directory from config or use default
114
+ config = get_config();
115
+ if config is not None {
116
+ dist_dir = config.get_client_dir() / 'dist';
117
+ } else {
118
+ dist_dir = base_path / '.jac' / 'client' / 'dist';
119
+ }
120
120
  css_link = '';
121
- css_file = dist_dir / 'main.css';
121
+ css_file = dist_dir / 'styles.css';
122
122
  if css_file.exists() {
123
123
  css_hash = hashlib.sha256(css_file.read_bytes()).hexdigest()[:8];
124
- css_link = f'<link rel="stylesheet" href="/static/main.css?hash={css_hash}"/>';
124
+ css_link = f'<link rel="stylesheet" href="/static/styles.css?hash={css_hash}"/>';
125
+ }
126
+ # Get meta data from config
127
+ client_cfg = config.get_plugin_config("client") if config else None;
128
+ meta_data = client_cfg.get("app_meta_data", {}) if client_cfg else {};
129
+ charset = meta_data.get("charset", "UTF-8");
130
+ title = meta_data.get("title", function_name);
131
+ viewport = meta_data.get("viewport", "width=device-width, initial-scale=1");
132
+ description = meta_data.get("description", None);
133
+ robots = meta_data.get("robots", "index, follow");
134
+ canonical = meta_data.get("canonical", None);
135
+ og_type = meta_data.get("og_type", "website");
136
+ og_title = meta_data.get("og_title", title);
137
+ og_description = meta_data.get("og_description", None);
138
+ og_url = meta_data.get("og_url", None);
139
+ og_image = meta_data.get("og_image", None);
140
+ theme_color = meta_data.get("theme_color", "#ffffff");
141
+ icon = meta_data.get("icon", None);
142
+ # Build head content from TOML metadata
143
+ head_content = f'<meta charset="{html.escape(charset)}"/>\n <meta name="viewport" content="{html.escape(
144
+ viewport
145
+ )}"/>\n <title>{html.escape(title)}</title>';
146
+ head_content += f'\n <meta name="robots" content="{html.escape(robots)}"/>';
147
+ head_content += f'\n <meta name="theme-color" content="{html.escape(
148
+ theme_color
149
+ )}"/>';
150
+ head_content += f'\n <meta property="og:type" content="{html.escape(
151
+ og_type
152
+ )}"/>';
153
+ head_content += f'\n <meta property="og:title" content="{html.escape(
154
+ og_title
155
+ )}"/>';
156
+ if description {
157
+ head_content += f'\n <meta name="description" content="{html.escape(
158
+ description
159
+ )}"/>';
160
+ }
161
+ if canonical {
162
+ head_content += f'\n <link rel="canonical" href="{html.escape(
163
+ canonical
164
+ )}"/>';
165
+ }
166
+ if icon {
167
+ head_content += f'\n <link rel="icon" href="{html.escape(icon)}"/>';
168
+ }
169
+ if og_url {
170
+ head_content += f'\n <meta property="og:url" content="{html.escape(
171
+ og_url
172
+ )}"/>';
173
+ }
174
+ if og_image {
175
+ head_content += f'\n <meta property="og:image" content="{html.escape(
176
+ og_image
177
+ )}"/>';
178
+ }
179
+ if og_description {
180
+ head_content += f'\n <meta property="og:description" content="{html.escape(
181
+ og_description
182
+ )}"/>';
125
183
  }
126
- head_content = f'<meta charset="utf-8"/>\n <title>{html.escape(
127
- function_name
128
- )}</title>';
129
184
  if css_link {
130
185
  head_content += f"\n {css_link}";
131
186
  }
132
187
  page = f'<!DOCTYPE html><html lang="en"><head>{head_content}</head><body><div id="root"></div><script src="/static/client.js?hash={bundle_hash}" defer></script></body></html>';
133
- return {'html': page, 'bundle_hash': bundle_hash, 'bundle_code': self._bundle.code};
188
+ return {
189
+ 'html': page,
190
+ 'bundle_hash': bundle_hash,
191
+ 'bundle_code': introspector._bundle.code
192
+ };
134
193
  }
@@ -65,9 +65,11 @@ impl __jacSpawn(left: str, right: str = "", fields: dict = {}) -> any {
65
65
  );
66
66
  if not response.ok {
67
67
  error_text = await response.json();
68
- raise Exception(f"Walker {walker} failed: {error_text}") ;
68
+ walker_name = f"{left}/{right}" if right else left;
69
+ raise Exception(f"Walker {walker_name} failed: {error_text}") ;
69
70
  }
70
- return await response.json();
71
+ payload = await response.json();
72
+ return payload["data"] if payload["data"] else {};
71
73
  }
72
74
 
73
75
  impl jacSpawn(left: str, right: str = "", fields: dict = {}) -> any {
@@ -87,12 +89,18 @@ impl __jacCallFunction(function_name: str, args: dict = {}) -> any {
87
89
  "body": JSON.stringify({"args": args})
88
90
  }
89
91
  );
90
- if not response.ok {
91
- error_text = await response.text();
92
- raise Exception(f"Function {function_name} failed: {error_text}") ;
92
+ payload = await response.json();
93
+ if not payload["ok"] {
94
+ error_msg = payload["error"] if payload["error"] else "Unknown error";
95
+ raise Exception(f"Function {function_name} failed: {error_msg}") ;
93
96
  }
94
- data = JSON.parse(await response.text());
95
- return data["result"];
97
+ result = None;
98
+ try {
99
+ if payload["data"] and payload["data"]["result"] {
100
+ result = payload["data"]["result"];
101
+ }
102
+ } except Exception { }
103
+ return result;
96
104
  }
97
105
 
98
106
  impl jacSignup(username: str, password: str) -> dict {
@@ -106,7 +114,10 @@ impl jacSignup(username: str, password: str) -> dict {
106
114
  );
107
115
  if response.ok {
108
116
  data = JSON.parse(await response.text());
109
- token = data["token"];
117
+ token = None;
118
+ if data["data"] and data["data"]["token"] {
119
+ token = data["data"]["token"];
120
+ }
110
121
  if token {
111
122
  __setLocalStorage("jac_token", token);
112
123
  return {"success": True, "token": token, "username": username};
@@ -139,7 +150,14 @@ impl jacLogin(username: str, password: str) -> bool {
139
150
  );
140
151
  if response.ok {
141
152
  data = JSON.parse(await response.text());
142
- token = data["token"];
153
+ console.log("data", data);
154
+ token = None;
155
+ try {
156
+ if data["data"] and data["data"]["token"] {
157
+ token = data["data"]["token"];
158
+ }
159
+ } except Exception { }
160
+ console.log("token", token);
143
161
  if token {
144
162
  __setLocalStorage("jac_token", token);
145
163
  return True;
@@ -80,7 +80,7 @@ class JacClientPluginConfig {
80
80
  "name": "npm",
81
81
  "dev_name": "npm.dev",
82
82
  "cli_flag": "--cl",
83
- "install_dir": ".client-build/.jac-client.configs",
83
+ "install_dir": ".jac/client/configs",
84
84
  "install_handler": _npm_install_handler,
85
85
  "install_all_handler": _npm_install_all_handler,
86
86
  "remove_handler": _npm_remove_handler
@@ -152,13 +152,13 @@ def _regenerate_and_install(project_dir: Path) -> None {
152
152
  bundler = ViteBundler(project_dir);
153
153
  bundler.create_package_json();
154
154
 
155
- # Install to .client-build/ directory where babel processor expects it
156
- build_dir = project_dir / '.client-build';
157
- build_dir.mkdir(exist_ok=True);
155
+ # Install to .jac/client/ directory where babel processor expects it
156
+ client_dir = bundler._get_client_dir();
157
+ client_dir.mkdir(parents=True, exist_ok=True);
158
158
 
159
- # Copy package.json to .client-build/ for npm install
160
- configs_package_json = build_dir / '.jac-client.configs' / 'package.json';
161
- build_package_json = build_dir / 'package.json';
159
+ # Copy package.json to .jac/client/ for npm install
160
+ configs_package_json = client_dir / 'configs' / 'package.json';
161
+ build_package_json = client_dir / 'package.json';
162
162
  if configs_package_json.exists() {
163
163
  shutil.copy2(configs_package_json, build_package_json);
164
164
  }
@@ -166,7 +166,7 @@ def _regenerate_and_install(project_dir: Path) -> None {
166
166
  try {
167
167
  subprocess.run(
168
168
  ["npm", "install"],
169
- cwd=build_dir,
169
+ cwd=client_dir,
170
170
  check=True,
171
171
  capture_output=True,
172
172
  text=True
@@ -182,10 +182,10 @@ def _regenerate_and_install(project_dir: Path) -> None {
182
182
  if build_package_json.exists() {
183
183
  build_package_json.unlink();
184
184
  }
185
- # Move package-lock.json to .jac-client.configs/ if it exists
186
- build_package_lock = build_dir / 'package-lock.json';
185
+ # Move package-lock.json to configs/ if it exists
186
+ build_package_lock = client_dir / 'package-lock.json';
187
187
  if build_package_lock.exists() {
188
- configs_package_lock = build_dir / '.jac-client.configs' / 'package-lock.json';
188
+ configs_package_lock = client_dir / 'configs' / 'package-lock.json';
189
189
  if configs_package_lock.exists() {
190
190
  configs_package_lock.unlink();
191
191
  }
@@ -52,6 +52,7 @@ class ViteCompiler {
52
52
  source_root: (Path | None) = None
53
53
  ) -> None;
54
54
 
55
+ def _get_client_dir(self: ViteCompiler) -> Path;
55
56
  def _copy_js_file(self: ViteCompiler, js_path: Path, source_root: Path) -> None;
56
57
  def _copy_ts_file(self: ViteCompiler, ts_path: Path, source_root: Path) -> None;
57
58
  def _copy_asset_file(
@@ -59,7 +60,7 @@ class ViteCompiler {
59
60
  ) -> None;
60
61
 
61
62
  def copy_root_assets(self: ViteCompiler) -> None;
62
- def create_entry_file(self: ViteCompiler) -> None;
63
+ def create_entry_file(self: ViteCompiler, module_path: Path) -> None;
63
64
  def compile_and_bundle(
64
65
  self: ViteCompiler, module: ModuleType, module_path: Path
65
66
  ) -> tuple[str, str, list[str], list[str]];
@@ -17,21 +17,21 @@ impl BabelProcessor.compile(self: BabelProcessor) -> None {
17
17
  bundler._ensure_root_package_json();
18
18
  try {
19
19
  # Ensure dependencies are installed (check if node_modules exists)
20
- build_dir = self.project_dir / '.client-build';
21
- node_modules = build_dir / 'node_modules';
20
+ client_dir = bundler._get_client_dir();
21
+ node_modules = client_dir / 'node_modules';
22
22
  if not node_modules.exists() {
23
- # Temporarily copy package.json to .client-build/ for npm install
24
- build_package_json = build_dir / 'package.json';
25
- configs_package_json = build_dir / '.jac-client.configs' / 'package.json';
23
+ # Temporarily copy package.json to .jac/client/ for npm install
24
+ build_package_json = client_dir / 'package.json';
25
+ configs_package_json = client_dir / 'configs' / 'package.json';
26
26
  if configs_package_json.exists() and not build_package_json.exists() {
27
27
  import shutil;
28
28
  shutil.copy2(configs_package_json, build_package_json);
29
29
  }
30
30
  try {
31
- # Install to .client-build/node_modules
31
+ # Install to .jac/client/node_modules
32
32
  subprocess.run(
33
33
  ['npm', 'install'],
34
- cwd=build_dir,
34
+ cwd=client_dir,
35
35
  check=True,
36
36
  capture_output=True,
37
37
  text=True
@@ -47,27 +47,32 @@ impl BabelProcessor.compile(self: BabelProcessor) -> None {
47
47
  }
48
48
  }
49
49
 
50
- # Temporarily copy package.json to .client-build/ for npm run compile
51
- build_package_json = build_dir / 'package.json';
52
- configs_package_json = build_dir / '.jac-client.configs' / 'package.json';
50
+ # Temporarily copy package.json to .jac/client/ for npm run compile
51
+ build_package_json = client_dir / 'package.json';
52
+ configs_package_json = client_dir / 'configs' / 'package.json';
53
53
  if configs_package_json.exists() and not build_package_json.exists() {
54
54
  import shutil;
55
55
  shutil.copy2(configs_package_json, build_package_json);
56
56
  }
57
57
  command = ['npm', 'run', 'compile'];
58
- subprocess.run(
59
- command, cwd=build_dir, check=True, capture_output=True, text=True
58
+ result = subprocess.run(
59
+ command, cwd=client_dir, capture_output=True, text=True
60
60
  );
61
+ if result.returncode != 0 {
62
+ # Show the actual error from npm/babel
63
+ error_output = result.stderr or result.stdout or "Unknown error";
64
+ raise RuntimeError(f"Client bundle compilation failed:\n{error_output}") ;
65
+ }
61
66
  } finally {
62
- # Clean up temporary package.json in .client-build/
63
- build_package_json = build_dir / 'package.json';
67
+ # Clean up temporary package.json in .jac/client/
68
+ build_package_json = client_dir / 'package.json';
64
69
  if build_package_json.exists() {
65
70
  build_package_json.unlink();
66
71
  }
67
- # Move package-lock.json to .jac-client.configs/ if it exists
68
- build_package_lock = build_dir / 'package-lock.json';
72
+ # Move package-lock.json to configs/ if it exists
73
+ build_package_lock = client_dir / 'package-lock.json';
69
74
  if build_package_lock.exists() {
70
- configs_package_lock = build_dir / '.jac-client.configs' / 'package-lock.json';
75
+ configs_package_lock = client_dir / 'configs' / 'package-lock.json';
71
76
  if configs_package_lock.exists() {
72
77
  configs_package_lock.unlink();
73
78
  }
@@ -1,5 +1,18 @@
1
- """Compile module and dependencies, then bundle with Vite."""
1
+ """Get the client build directory from project config."""
2
+ impl ViteCompiler._get_client_dir(self: ViteCompiler) -> Path {
3
+ # Try to get from project config
4
+ try {
5
+ import from jaclang.project.config { get_config }
6
+ config = get_config();
7
+ if config is not None {
8
+ return config.get_client_dir();
9
+ }
10
+ } except ImportError { }
11
+ # Fallback to default
12
+ return self.project_dir / '.jac' / 'client';
13
+ }
2
14
 
15
+ """Compile module and dependencies, then bundle with Vite."""
3
16
  impl ViteCompiler.compile_and_bundle(
4
17
  self: ViteCompiler, module: ModuleType, module_path: Path
5
18
  ) -> tuple[str, str, list[str], list[str]] {
@@ -16,14 +29,13 @@ impl ViteCompiler.compile_and_bundle(
16
29
  collected_globals=collected_globals
17
30
  );
18
31
  self.copy_root_assets();
19
- self.create_entry_file();
32
+ self.create_entry_file(module_path);
20
33
  self.babel_processor.compile();
34
+ client_dir = self._get_client_dir();
21
35
  self.babel_processor.copy_assets_after_compile(
22
- self.compiled_dir,
23
- (self.project_dir / '.client-build' / 'build'),
24
- self.asset_processor
36
+ self.compiled_dir, (client_dir / 'build'), self.asset_processor
25
37
  );
26
- entry_file = self.project_dir / '.client-build' / 'build' / 'main.js';
38
+ entry_file = client_dir / 'build' / '_entry.js';
27
39
  self.vite_bundler.build(entry_file=entry_file);
28
40
  (bundle_code, bundle_hash) = self.vite_bundler.read_bundle();
29
41
  client_exports = sorted(collected_exports);
@@ -32,9 +44,12 @@ impl ViteCompiler.compile_and_bundle(
32
44
  }
33
45
 
34
46
  """Create the main entry file for Vite bundling."""
35
- impl ViteCompiler.create_entry_file(self: ViteCompiler) -> None {
36
- entry_file = self.compiled_dir / 'main.js';
37
- entry_content = 'import React from "react";\nimport { createRoot } from "react-dom/client";\nimport { app as App } from "./app.js";\n\nconst root = createRoot(document.getElementById("root"));\nroot.render(<App />);\n';
47
+ impl ViteCompiler.create_entry_file(self: ViteCompiler, module_path: Path) -> None {
48
+ # Use _entry.js to avoid conflict with compiled modules that may be named main.js
49
+ entry_file = self.compiled_dir / '_entry.js';
50
+ # Derive the app module filename from the entry point (e.g., main.jac -> main.js, app.jac -> app.js)
51
+ app_module_name = module_path.stem;
52
+ entry_content = f'import React from "react";\nimport {{ createRoot }} from "react-dom/client";\nimport {{ app as App }} from "./{app_module_name}.js";\n\nconst root = createRoot(document.getElementById("root"));\nroot.render(React.createElement(App, null));\n';
38
53
  entry_file.write_text(entry_content, encoding='utf-8');
39
54
  }
40
55
 
@@ -46,7 +61,7 @@ impl ViteCompiler.copy_root_assets(self: ViteCompiler) -> None {
46
61
  self.asset_processor.copy_assets(root_assets_dir, compiled_assets_dir);
47
62
  }
48
63
  # Copy configured_asset files from root assets/ to build assets/ (bypassing compiled)
49
- build_assets_dir = self.project_dir / '.client-build' / 'build' / 'assets';
64
+ build_assets_dir = self._get_client_dir() / 'build' / 'assets';
50
65
  if (root_assets_dir.exists() and root_assets_dir.is_dir()) {
51
66
  self.asset_processor.copy_custom_asset_types(root_assets_dir, build_assets_dir);
52
67
  }
@@ -149,9 +164,29 @@ impl ViteCompiler.compile_dependencies_recursively(
149
164
  combined_js = self.jac_compiler.add_runtime_imports(module_js);
150
165
  try {
151
166
  relative_path = module_path.relative_to(source_root);
152
- output_path = self.compiled_dir / relative_path.with_suffix('.js');
167
+ # Handle compound extensions like .cl.jac, .impl.jac -> .js
168
+ rel_str = str(relative_path);
169
+ for compound_ext in ['.cl.jac', '.impl.jac', '.test.jac'] {
170
+ if rel_str.endswith(compound_ext) {
171
+ rel_str = rel_str[:-len(compound_ext)] + '.js';
172
+ break;
173
+ }
174
+ } else {
175
+ rel_str = str(relative_path.with_suffix('.js'));
176
+ }
177
+ output_path = self.compiled_dir / rel_str;
153
178
  } except ValueError {
154
- output_path = self.compiled_dir / f"{module_path.stem}.js";
179
+ # Handle compound extensions in filename
180
+ name = module_path.name;
181
+ for compound_ext in ['.cl.jac', '.impl.jac', '.test.jac'] {
182
+ if name.endswith(compound_ext) {
183
+ name = name[:-len(compound_ext)] + '.js';
184
+ break;
185
+ }
186
+ } else {
187
+ name = module_path.stem + '.js';
188
+ }
189
+ output_path = self.compiled_dir / name;
155
190
  }
156
191
  output_path.parent.mkdir(parents=True, exist_ok=True);
157
192
  output_path.write_text(combined_js, encoding='utf-8');
@@ -228,19 +263,21 @@ impl ViteCompiler.init(
228
263
  ) ;
229
264
  }
230
265
  self.vite_package_json = vite_package_json;
231
- # If package.json is in .client-build/.jac-client.configs/, go up two levels to get project root
266
+ # Detect project root: package.json may be in .jac/client/configs/ or project root
232
267
  if (
233
- vite_package_json.parent.name == '.jac-client.configs'
234
- and vite_package_json.parent.parent.name == '.client-build'
268
+ vite_package_json.parent.name == 'configs'
269
+ and vite_package_json.parent.parent.name == 'client'
270
+ and vite_package_json.parent.parent.parent.name == '.jac'
235
271
  ) {
236
- self.project_dir = vite_package_json.parent.parent.parent;
237
- } elif (vite_package_json.parent.name == '.jac-client.configs') {
272
+ # .jac/client/configs/package.json -> go up 3 levels to project root
273
+ self.project_dir = vite_package_json.parent.parent.parent.parent;
274
+ } elif (vite_package_json.parent.name == 'configs') {
238
275
  self.project_dir = vite_package_json.parent.parent;
239
276
  } else {
240
277
  self.project_dir = vite_package_json.parent;
241
278
  }
242
279
  self.runtime_path = runtime_path;
243
- self.compiled_dir = self.project_dir / '.client-build' / 'compiled';
280
+ self.compiled_dir = self._get_client_dir() / 'compiled';
244
281
  self.jac_compiler = JacToJSCompiler(
245
282
  compile_to_js_func, extract_exports_func, extract_globals_func
246
283
  );