jac-client 0.2.6__py3-none-any.whl → 0.2.11__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 (119) hide show
  1. jac_client/examples/all-in-one/{src/button.jac → button.jac} +4 -3
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +47 -0
  3. jac_client/examples/all-in-one/components/Header.jac +17 -0
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +64 -0
  5. jac_client/examples/all-in-one/components/Summary.jac +76 -0
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +188 -0
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +62 -0
  8. jac_client/examples/all-in-one/components/TransactionList.jac +44 -0
  9. jac_client/examples/all-in-one/components/button.jac +8 -0
  10. jac_client/examples/all-in-one/components/navigation.jac +126 -0
  11. jac_client/examples/all-in-one/constants/categories.jac +36 -0
  12. jac_client/examples/all-in-one/constants/clients.jac +12 -0
  13. jac_client/examples/all-in-one/context/BudgetContext.jac +31 -0
  14. jac_client/examples/all-in-one/hooks/useBudget.jac +122 -0
  15. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +37 -0
  16. jac_client/examples/all-in-one/main.jac +542 -0
  17. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +140 -0
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +157 -0
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +124 -0
  20. jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +65 -0
  21. jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +675 -0
  22. jac_client/examples/all-in-one/pages/loginPage.jac +127 -0
  23. jac_client/examples/all-in-one/pages/nestedDemo.jac +54 -0
  24. jac_client/examples/all-in-one/pages/notFound.jac +18 -0
  25. jac_client/examples/all-in-one/pages/signupPage.jac +127 -0
  26. jac_client/examples/all-in-one/utils/formatters.jac +49 -0
  27. jac_client/examples/asset-serving/css-with-image/main.jac +92 -0
  28. jac_client/examples/asset-serving/image-asset/main.jac +56 -0
  29. jac_client/examples/asset-serving/import-alias/main.jac +109 -0
  30. jac_client/examples/basic/main.jac +23 -0
  31. jac_client/examples/basic-auth/main.jac +363 -0
  32. jac_client/examples/basic-auth-with-router/main.jac +451 -0
  33. jac_client/examples/basic-full-stack/main.jac +362 -0
  34. jac_client/examples/css-styling/js-styling/main.jac +63 -0
  35. jac_client/examples/css-styling/material-ui/main.jac +122 -0
  36. jac_client/examples/css-styling/pure-css/main.jac +55 -0
  37. jac_client/examples/css-styling/sass-example/main.jac +55 -0
  38. jac_client/examples/css-styling/styled-components/main.jac +62 -0
  39. jac_client/examples/css-styling/tailwind-example/main.jac +74 -0
  40. jac_client/examples/full-stack-with-auth/main.jac +696 -0
  41. jac_client/examples/little-x/main.jac +681 -0
  42. jac_client/examples/little-x/src/submit-button.jac +15 -14
  43. jac_client/examples/nested-folders/nested-advance/main.jac +26 -0
  44. jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +4 -6
  45. jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +9 -13
  46. jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +29 -32
  47. jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +12 -18
  48. jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +7 -5
  49. jac_client/examples/nested-folders/nested-basic/src/button.jac +4 -3
  50. jac_client/examples/nested-folders/nested-basic/src/components/button.jac +4 -3
  51. jac_client/examples/ts-support/main.jac +35 -0
  52. jac_client/examples/with-router/main.jac +286 -0
  53. jac_client/plugin/cli.jac +507 -470
  54. jac_client/plugin/client.jac +30 -12
  55. jac_client/plugin/client_runtime.cl.jac +25 -15
  56. jac_client/plugin/impl/client.impl.jac +126 -26
  57. jac_client/plugin/impl/client_runtime.impl.jac +182 -10
  58. jac_client/plugin/plugin_config.jac +216 -34
  59. jac_client/plugin/src/__init__.jac +0 -2
  60. jac_client/plugin/src/compiler.jac +2 -2
  61. jac_client/plugin/src/config_loader.jac +1 -0
  62. jac_client/plugin/src/desktop_config.jac +31 -0
  63. jac_client/plugin/src/impl/compiler.impl.jac +99 -30
  64. jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
  65. jac_client/plugin/src/impl/desktop_config.impl.jac +191 -0
  66. jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
  67. jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
  68. jac_client/plugin/src/impl/vite_bundler.impl.jac +384 -144
  69. jac_client/plugin/src/package_installer.jac +1 -1
  70. jac_client/plugin/src/targets/desktop/sidecar/main.py +144 -0
  71. jac_client/plugin/src/targets/desktop_target.jac +37 -0
  72. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +2347 -0
  73. jac_client/plugin/src/targets/impl/registry.impl.jac +64 -0
  74. jac_client/plugin/src/targets/impl/web_target.impl.jac +157 -0
  75. jac_client/plugin/src/targets/register.jac +21 -0
  76. jac_client/plugin/src/targets/registry.jac +87 -0
  77. jac_client/plugin/src/targets/web_target.jac +35 -0
  78. jac_client/plugin/src/vite_bundler.jac +15 -1
  79. jac_client/plugin/utils/__init__.jac +3 -0
  80. jac_client/plugin/utils/bun_installer.jac +16 -0
  81. jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
  82. jac_client/templates/client.jacpack +72 -0
  83. jac_client/templates/fullstack.jacpack +61 -0
  84. jac_client/tests/conftest.py +110 -52
  85. jac_client/tests/fixtures/spawn_test/app.jac +64 -70
  86. jac_client/tests/fixtures/with-ts/app.jac +28 -28
  87. jac_client/tests/test_cli.py +280 -113
  88. jac_client/tests/test_e2e.py +232 -0
  89. jac_client/tests/test_helpers.py +58 -0
  90. jac_client/tests/test_it.py +325 -154
  91. jac_client/tests/test_it_desktop.py +891 -0
  92. {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/METADATA +20 -11
  93. jac_client-0.2.11.dist-info/RECORD +113 -0
  94. {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/WHEEL +1 -1
  95. jac_client/examples/all-in-one/src/app.jac +0 -841
  96. jac_client/examples/all-in-one/src/components/button.jac +0 -7
  97. jac_client/examples/asset-serving/css-with-image/src/app.jac +0 -88
  98. jac_client/examples/asset-serving/image-asset/src/app.jac +0 -55
  99. jac_client/examples/asset-serving/import-alias/src/app.jac +0 -111
  100. jac_client/examples/basic/src/app.jac +0 -21
  101. jac_client/examples/basic-auth/src/app.jac +0 -377
  102. jac_client/examples/basic-auth-with-router/src/app.jac +0 -464
  103. jac_client/examples/basic-full-stack/src/app.jac +0 -365
  104. jac_client/examples/css-styling/js-styling/src/app.jac +0 -84
  105. jac_client/examples/css-styling/material-ui/src/app.jac +0 -122
  106. jac_client/examples/css-styling/pure-css/src/app.jac +0 -64
  107. jac_client/examples/css-styling/sass-example/src/app.jac +0 -64
  108. jac_client/examples/css-styling/styled-components/src/app.jac +0 -71
  109. jac_client/examples/css-styling/tailwind-example/src/app.jac +0 -63
  110. jac_client/examples/full-stack-with-auth/src/app.jac +0 -722
  111. jac_client/examples/little-x/src/app.jac +0 -719
  112. jac_client/examples/nested-folders/nested-advance/src/app.jac +0 -35
  113. jac_client/examples/ts-support/src/app.jac +0 -35
  114. jac_client/examples/with-router/src/app.jac +0 -323
  115. jac_client/plugin/src/babel_processor.jac +0 -18
  116. jac_client/plugin/src/impl/babel_processor.impl.jac +0 -84
  117. jac_client-0.2.6.dist-info/RECORD +0 -74
  118. {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/entry_points.txt +0 -0
  119. {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/top_level.txt +0 -0
@@ -5,10 +5,13 @@ configuration system, registering:
5
5
  - Plugin metadata (name, version)
6
6
  - Config schema for [plugins.client] section
7
7
  - npm dependency type for [dependencies.npm] section
8
+ - Project templates (client, fullstack)
8
9
  """
9
10
 
11
+ import os;
10
12
  import subprocess;
11
13
  import sys;
14
+ import json;
12
15
  import from pathlib { Path }
13
16
  import from typing { Any }
14
17
  import from jaclang.pycore.runtime { hookimpl }
@@ -68,6 +71,11 @@ class JacClientPluginConfig {
68
71
  "type": "dict",
69
72
  "default": {},
70
73
  "description": "TypeScript configuration overrides"
74
+ },
75
+ "configs": {
76
+ "type": "dict",
77
+ "default": {},
78
+ "description": "Generate config files for npm packages. Keys are config names (e.g., 'postcss', 'tailwind'), values are the config objects to export."
71
79
  }
72
80
  }
73
81
  };
@@ -79,15 +87,194 @@ class JacClientPluginConfig {
79
87
  return {
80
88
  "name": "npm",
81
89
  "dev_name": "npm.dev",
82
- "cli_flag": "--cl",
83
- "install_dir": ".client-build/.jac-client.configs",
90
+ "cli_flag": "--npm",
91
+ "install_dir": ".jac/client/configs",
84
92
  "install_handler": _npm_install_handler,
85
93
  "install_all_handler": _npm_install_all_handler,
86
94
  "remove_handler": _npm_remove_handler
87
95
  };
88
96
  }
97
+
98
+ """Register the client and fullstack project templates."""
99
+ @hookimpl
100
+ static def register_project_template -> list[dict[str, Any]] {
101
+ templates: list[dict[str, Any]] = [];
102
+
103
+ # Load templates from bundled JSON files
104
+ for template_name in ["client", "fullstack"] {
105
+ template = _load_template(template_name);
106
+ if template {
107
+ templates.append(template);
108
+ }
109
+ }
110
+
111
+ return templates;
112
+ }
113
+ }
114
+
115
+ # ===============================================================================
116
+ # Template Loader
117
+ # ===============================================================================
118
+ """Load a template from bundled JSON file."""
119
+ def _load_template(template_name: str) -> dict[str, Any] | None {
120
+ # Find the template JSON file relative to this module
121
+ # plugin_config.jac is in jac_client/plugin/, so .parent.parent = jac_client/
122
+ module_dir = Path(__file__).parent.parent; # jac_client directory
123
+ template_path = module_dir / "templates" / f"{template_name}.jacpack";
124
+
125
+ if not template_path.exists() {
126
+ # Fallback: look in installed package location
127
+ import from importlib.resources { files }
128
+ try {
129
+ package_files = files("jac_client");
130
+ template_path = Path(str(package_files)) / "templates" / f"{template_name}.jacpack";
131
+ } except Exception {
132
+ return None;
133
+ }
134
+ }
135
+
136
+ if not template_path.exists() {
137
+ return None;
138
+ }
139
+
140
+ try {
141
+ with open(template_path, "r") as f {
142
+ data = json.load(f);
143
+ }
144
+
145
+ # Attach the post_create hook
146
+ data["post_create"] = _post_create_client;
147
+
148
+ return data;
149
+ } except Exception as e {
150
+ print(
151
+ f"Warning: Could not load {template_name} template: {e}", file=sys.stderr
152
+ );
153
+ return None;
154
+ }
89
155
  }
90
156
 
157
+ # ===============================================================================
158
+ # Post-create Hook for Client Template
159
+ # ===============================================================================
160
+ """Post-create hook to ensure Bun is installed and optionally install packages."""
161
+ def _post_create_client(project_path: Path, project_name: str) -> None {
162
+ import from jac_client.plugin.src.vite_bundler { ViteBundler }
163
+ import from jaclang.cli.console { console }
164
+ import from jac_client.plugin.utils { ensure_bun_available }
165
+
166
+ # First, ensure Bun is installed (prompt to install if not)
167
+ console.print();
168
+ if not ensure_bun_available() {
169
+ console.warning("Bun is required for client development");
170
+ console.print(" After installing Bun, run: jac add --cl\n", style="muted");
171
+ return;
172
+ }
173
+
174
+ console.print(" ✔ Bun is installed", style="success");
175
+
176
+ # Check if package installation should be skipped
177
+ if os.environ.get("JAC_CLIENT_SKIP_NPM_INSTALL") {
178
+ console.print(
179
+ "\n ⏭ Skipping package installation (JAC_CLIENT_SKIP_NPM_INSTALL is set)",
180
+ style="muted"
181
+ );
182
+ console.print(
183
+ " Run 'jac add --npm' when ready to install packages.\n", style="muted"
184
+ );
185
+ return;
186
+ }
187
+
188
+ console.print();
189
+
190
+ try {
191
+ # Verify jac.toml exists
192
+ toml_path = project_path / "jac.toml";
193
+ if not toml_path.exists() {
194
+ print(
195
+ "Warning: jac.toml not found, skipping package installation",
196
+ file=sys.stderr
197
+ );
198
+ return;
199
+ }
200
+
201
+ # Create ViteBundler instance
202
+ bundler = ViteBundler(project_path);
203
+
204
+ # Generate package.json with default packages
205
+ bundler.create_package_json(project_name=project_name);
206
+
207
+ # Ensure .jac/client directory exists
208
+ client_dir = bundler._get_client_dir();
209
+ client_dir.mkdir(parents=True, exist_ok=True);
210
+
211
+ # Copy package.json to .jac/client/ for bun install
212
+ configs_package_json = client_dir / 'configs' / 'package.json';
213
+ build_package_json = client_dir / 'package.json';
214
+
215
+ if not configs_package_json.exists() {
216
+ print(
217
+ "Warning: package.json was not generated, skipping package installation",
218
+ file=sys.stderr
219
+ );
220
+ return;
221
+ }
222
+
223
+ shutil.copy2(configs_package_json, build_package_json);
224
+
225
+ # Run bun install (ensure bun is available first)
226
+ import from jac_client.plugin.utils { ensure_bun_available }
227
+ if not ensure_bun_available() {
228
+ console.warning("Bun is required. Install manually: https://bun.sh");
229
+ console.print(
230
+ " 💡 You can install packages later with: jac add --cl\n",
231
+ style="muted"
232
+ );
233
+ # Clean up temporary package.json
234
+ if build_package_json.exists() {
235
+ build_package_json.unlink();
236
+ }
237
+ return;
238
+ }
239
+
240
+ try {
241
+ console.print("\n ⏳ Installing packages...\n", style="cyan");
242
+ subprocess.run(['bun', 'install'], cwd=client_dir, check=True, timeout=300);
243
+
244
+ # Move bun.lockb to configs/ if it was created
245
+ build_bun_lockb = client_dir / 'bun.lockb';
246
+ configs_bun_lockb = client_dir / 'configs' / 'bun.lockb';
247
+ if build_bun_lockb.exists() {
248
+ if configs_bun_lockb.exists() {
249
+ configs_bun_lockb.unlink();
250
+ }
251
+ shutil.move(str(build_bun_lockb), str(configs_bun_lockb));
252
+ }
253
+
254
+ console.print(" ✔ Packages installed", style="success");
255
+ } except subprocess.CalledProcessError as e {
256
+ console.warning(f"Failed to install packages: {e}");
257
+ console.print(
258
+ " 💡 You can install packages later with: jac add --cl\n",
259
+ style="muted"
260
+ );
261
+ } finally {
262
+ # Clean up temporary package.json
263
+ if build_package_json.exists() {
264
+ build_package_json.unlink();
265
+ }
266
+ }
267
+ } except Exception as e {
268
+ console.warning(f"Could not install default packages: {e}");
269
+ console.print(
270
+ " 💡 You can install packages later with: jac add --cl\n", style="muted"
271
+ );
272
+ }
273
+ }
274
+
275
+ # ===============================================================================
276
+ # NPM Dependency Handlers
277
+ # ===============================================================================
91
278
  """Install an npm package."""
92
279
  def _npm_install_handler(
93
280
  config: JacConfig,
@@ -101,15 +288,13 @@ def _npm_install_handler(
101
288
  raise RuntimeError("No project root found") ;
102
289
  }
103
290
 
104
- project_dir = config.project_root;
105
-
106
291
  # Add to config (core JacConfig handles this)
107
- package_version = version or "latest";
292
+ package_version: str = version or "latest";
108
293
  config.add_dependency(package_name, package_version, dev=is_dev, dep_type="npm");
109
294
  config.save();
110
295
 
111
296
  # Generate package.json and run npm install
112
- _regenerate_and_install(project_dir);
297
+ _regenerate_and_install(Path(str(config.project_root)));
113
298
  }
114
299
 
115
300
  """Install all npm packages from jac.toml."""
@@ -118,7 +303,7 @@ def _npm_install_all_handler(config: JacConfig) -> None {
118
303
  raise RuntimeError("No project root found") ;
119
304
  }
120
305
 
121
- _regenerate_and_install(config.project_root);
306
+ _regenerate_and_install(Path(str(config.project_root)));
122
307
  }
123
308
 
124
309
  """Remove an npm package."""
@@ -129,7 +314,7 @@ def _npm_remove_handler(
129
314
  raise RuntimeError("No project root found") ;
130
315
  }
131
316
 
132
- project_dir = config.project_root;
317
+ project_dir: Path = Path(str(config.project_root));
133
318
 
134
319
  # Remove from config (core JacConfig handles this)
135
320
  result = config.remove_dependency(package_name, dev=is_dev, dep_type="npm");
@@ -144,52 +329,49 @@ def _npm_remove_handler(
144
329
  _regenerate_and_install(project_dir);
145
330
  }
146
331
 
147
- """Regenerate package.json from jac.toml and run npm install."""
332
+ """Regenerate package.json from jac.toml and run bun install."""
148
333
  def _regenerate_and_install(project_dir: Path) -> None {
149
334
  import from jac_client.plugin.src.vite_bundler { ViteBundler }
335
+ import from jac_client.plugin.utils { ensure_bun_available }
150
336
  import shutil;
151
337
 
338
+ # Ensure Bun is installed before proceeding (prompt to install if not)
339
+ if not ensure_bun_available() {
340
+ raise RuntimeError("Bun is required. Install manually: https://bun.sh") from None ;
341
+ }
342
+
152
343
  bundler = ViteBundler(project_dir);
153
344
  bundler.create_package_json();
154
345
 
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);
346
+ # Install to .jac/client/ directory
347
+ client_dir = bundler._get_client_dir();
348
+ client_dir.mkdir(parents=True, exist_ok=True);
158
349
 
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';
350
+ # Copy package.json to .jac/client/ for bun install
351
+ configs_package_json = client_dir / 'configs' / 'package.json';
352
+ build_package_json = client_dir / 'package.json';
162
353
  if configs_package_json.exists() {
163
354
  shutil.copy2(configs_package_json, build_package_json);
164
355
  }
165
356
 
166
357
  try {
167
- subprocess.run(
168
- ["npm", "install"],
169
- cwd=build_dir,
170
- check=True,
171
- capture_output=True,
172
- text=True
173
- );
358
+ # Run bun install
359
+ subprocess.run(['bun', 'install'], cwd=client_dir, check=True, timeout=300);
174
360
  } except subprocess.CalledProcessError as e {
175
- raise RuntimeError(f"Failed to install npm packages: {e.stderr}") from e ;
176
- } except FileNotFoundError {
177
- raise RuntimeError(
178
- "npm command not found. Ensure Node.js and npm are installed."
179
- ) from None ;
361
+ raise RuntimeError(f"Failed to install packages: {e}") from e ;
180
362
  } finally {
181
363
  # Clean up temporary package.json
182
364
  if build_package_json.exists() {
183
365
  build_package_json.unlink();
184
366
  }
185
- # Move package-lock.json to .jac-client.configs/ if it exists
186
- build_package_lock = build_dir / 'package-lock.json';
187
- if build_package_lock.exists() {
188
- configs_package_lock = build_dir / '.jac-client.configs' / 'package-lock.json';
189
- if configs_package_lock.exists() {
190
- configs_package_lock.unlink();
367
+ # Move bun.lockb to configs/ if it exists
368
+ build_bun_lockb = client_dir / 'bun.lockb';
369
+ if build_bun_lockb.exists() {
370
+ configs_bun_lockb = client_dir / 'configs' / 'bun.lockb';
371
+ if configs_bun_lockb.exists() {
372
+ configs_bun_lockb.unlink();
191
373
  }
192
- build_package_lock.rename(configs_package_lock);
374
+ build_bun_lockb.rename(configs_bun_lockb);
193
375
  }
194
376
  }
195
377
  }
@@ -1,6 +1,5 @@
1
1
  """Vite client bundle processing modules."""
2
2
  import from jac_client.plugin.src.asset_processor { AssetProcessor }
3
- import from jac_client.plugin.src.babel_processor { BabelProcessor }
4
3
  import from jac_client.plugin.src.compiler { ViteCompiler }
5
4
  import from jac_client.plugin.src.config_loader { JacClientConfig }
6
5
  import from jac_client.plugin.src.import_processor { ImportProcessor }
@@ -10,7 +9,6 @@ import from jac_client.plugin.src.vite_bundler { ViteBundler }
10
9
 
11
10
  glob __all__ = [
12
11
  'AssetProcessor',
13
- 'BabelProcessor',
14
12
  'ViteCompiler',
15
13
  'JacClientConfig',
16
14
  'ImportProcessor',
@@ -6,7 +6,6 @@ import from types { ModuleType }
6
6
  import from typing { TYPE_CHECKING, Any }
7
7
  import from jaclang.runtimelib.client_bundle { ClientBundleError }
8
8
  import from .asset_processor { AssetProcessor }
9
- import from .babel_processor { BabelProcessor }
10
9
  import from .import_processor { ImportProcessor }
11
10
  import from .jac_to_js { JacToJSCompiler }
12
11
  import from .vite_bundler { ViteBundler }
@@ -52,6 +51,7 @@ class ViteCompiler {
52
51
  source_root: (Path | None) = None
53
52
  ) -> None;
54
53
 
54
+ def _get_client_dir(self: ViteCompiler) -> Path;
55
55
  def _copy_js_file(self: ViteCompiler, js_path: Path, source_root: Path) -> None;
56
56
  def _copy_ts_file(self: ViteCompiler, ts_path: Path, source_root: Path) -> None;
57
57
  def _copy_asset_file(
@@ -59,7 +59,7 @@ class ViteCompiler {
59
59
  ) -> None;
60
60
 
61
61
  def copy_root_assets(self: ViteCompiler) -> None;
62
- def create_entry_file(self: ViteCompiler) -> None;
62
+ def create_entry_file(self: ViteCompiler, module_path: Path) -> None;
63
63
  def compile_and_bundle(
64
64
  self: ViteCompiler, module: ModuleType, module_path: Path
65
65
  ) -> tuple[str, str, list[str], list[str]];
@@ -21,6 +21,7 @@ class JacClientConfig(PluginConfigBase) {
21
21
  def save(self: JacClientConfig) -> None;
22
22
  def get_vite_config(self: JacClientConfig) -> dict[str, Any];
23
23
  def get_ts_config(self: JacClientConfig) -> dict[str, Any];
24
+ def get_configs(self: JacClientConfig) -> dict[str, Any];
24
25
  def get_package_config(self: JacClientConfig) -> dict[str, Any];
25
26
  def add_dependency(
26
27
  self: JacClientConfig, name: str, version: str, is_dev: bool = False
@@ -0,0 +1,31 @@
1
+ """Configuration loader for Desktop target (Tauri).
2
+
3
+ This module provides configuration access for the desktop target.
4
+ Configuration is loaded from jac.toml under [desktop] section.
5
+
6
+ Similar to JacClientConfig but loads [desktop] directly (not under [plugins]).
7
+ """
8
+ import from pathlib { Path }
9
+ import from typing { Any }
10
+ import from jaclang.project.config { JacConfig, get_config }
11
+
12
+ """Desktop target configuration loader.
13
+
14
+ Provides access to desktop/Tauri configuration
15
+ from the [desktop] section of jac.toml.
16
+ """
17
+ class DesktopConfig {
18
+ has project_dir: Path | None = None,
19
+ _config: dict[str, Any] | None = None,
20
+ _jac_config: JacConfig | None = None;
21
+
22
+ def init(self: DesktopConfig, project_dir: Path | None = None) -> None;
23
+ def load(self: DesktopConfig) -> dict[str, Any];
24
+ def get_desktop_config(self: DesktopConfig) -> dict[str, Any];
25
+ def get_window_config(self: DesktopConfig) -> dict[str, Any];
26
+ def get_platforms_config(self: DesktopConfig) -> dict[str, Any];
27
+ def get_features_config(self: DesktopConfig) -> dict[str, Any];
28
+ def validate(self: DesktopConfig) -> None;
29
+ def get_default_config(self: DesktopConfig) -> dict[str, Any];
30
+ # def _get_jac_config(self: DesktopConfig) -> JacConfig;
31
+ }
@@ -1,9 +1,21 @@
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]] {
6
- self.compile_runtime_utils();
7
19
  (module_js, mod, module_manifest) = self.jac_compiler.compile_module(module_path);
8
20
  collected_exports: set[str] = set(
9
21
  self.jac_compiler.extract_exports(module_manifest)
@@ -15,15 +27,14 @@ impl ViteCompiler.compile_and_bundle(
15
27
  collected_exports=collected_exports,
16
28
  collected_globals=collected_globals
17
29
  );
30
+ # Compile runtime AFTER dependencies so it overwrites any dependency-compiled
31
+ # version of client_runtime.js with the proper ES module exports
32
+ self.compile_runtime_utils();
18
33
  self.copy_root_assets();
19
- self.create_entry_file();
20
- self.babel_processor.compile();
21
- self.babel_processor.copy_assets_after_compile(
22
- self.compiled_dir,
23
- (self.project_dir / '.client-build' / 'build'),
24
- self.asset_processor
25
- );
26
- entry_file = self.project_dir / '.client-build' / 'build' / 'main.js';
34
+ self.create_entry_file(module_path);
35
+ # Vite handles JSX/TSX transpilation natively with Bun - no Babel needed
36
+ # Vite builds directly from compiled/ directory
37
+ entry_file = self.compiled_dir / '_entry.js';
27
38
  self.vite_bundler.build(entry_file=entry_file);
28
39
  (bundle_code, bundle_hash) = self.vite_bundler.read_bundle();
29
40
  client_exports = sorted(collected_exports);
@@ -32,9 +43,12 @@ impl ViteCompiler.compile_and_bundle(
32
43
  }
33
44
 
34
45
  """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';
46
+ impl ViteCompiler.create_entry_file(self: ViteCompiler, module_path: Path) -> None {
47
+ # Use _entry.js to avoid conflict with compiled modules that may be named main.js
48
+ entry_file = self.compiled_dir / '_entry.js';
49
+ # Derive the app module filename from the entry point (e.g., main.jac -> main.js, app.jac -> app.js)
50
+ app_module_name = module_path.stem;
51
+ entry_content = f'import React from "react";\nimport {{ createRoot }} from "react-dom/client";\nimport {{ app as App }} from "./{app_module_name}.js";\nimport {{ JacClientErrorBoundary, ErrorFallback }} from "@jac/runtime";\n\nconst root = createRoot(document.getElementById("root"));\nroot.render(\n\tReact.createElement(\n\t\tJacClientErrorBoundary,{{ FallbackComponent: ErrorFallback }},\n\t\tReact.createElement(App, null)\n\t)\n);\n';
38
52
  entry_file.write_text(entry_content, encoding='utf-8');
39
53
  }
40
54
 
@@ -44,11 +58,10 @@ impl ViteCompiler.copy_root_assets(self: ViteCompiler) -> None {
44
58
  compiled_assets_dir = self.compiled_dir / 'assets';
45
59
  if (root_assets_dir.exists() and root_assets_dir.is_dir()) {
46
60
  self.asset_processor.copy_assets(root_assets_dir, compiled_assets_dir);
47
- }
48
- # Copy configured_asset files from root assets/ to build assets/ (bypassing compiled)
49
- build_assets_dir = self.project_dir / '.client-build' / 'build' / 'assets';
50
- if (root_assets_dir.exists() and root_assets_dir.is_dir()) {
51
- self.asset_processor.copy_custom_asset_types(root_assets_dir, build_assets_dir);
61
+ # Copy configured custom asset types to compiled/assets/
62
+ self.asset_processor.copy_custom_asset_types(
63
+ root_assets_dir, compiled_assets_dir
64
+ );
52
65
  }
53
66
  }
54
67
 
@@ -149,12 +162,46 @@ impl ViteCompiler.compile_dependencies_recursively(
149
162
  combined_js = self.jac_compiler.add_runtime_imports(module_js);
150
163
  try {
151
164
  relative_path = module_path.relative_to(source_root);
152
- output_path = self.compiled_dir / relative_path.with_suffix('.js');
165
+ # Handle compound extensions like .cl.jac, .impl.jac -> .js
166
+ rel_str = str(relative_path);
167
+ for compound_ext in ['.cl.jac', '.impl.jac', '.test.jac'] {
168
+ if rel_str.endswith(compound_ext) {
169
+ rel_str = rel_str[:-len(compound_ext)] + '.js';
170
+ break;
171
+ }
172
+ } else {
173
+ rel_str = str(relative_path.with_suffix('.js'));
174
+ }
175
+ output_path = self.compiled_dir / rel_str;
153
176
  } except ValueError {
154
- output_path = self.compiled_dir / f"{module_path.stem}.js";
177
+ # Handle compound extensions in filename
178
+ name = module_path.name;
179
+ for compound_ext in ['.cl.jac', '.impl.jac', '.test.jac'] {
180
+ if name.endswith(compound_ext) {
181
+ name = name[:-len(compound_ext)] + '.js';
182
+ break;
183
+ }
184
+ } else {
185
+ name = module_path.stem + '.js';
186
+ }
187
+ output_path = self.compiled_dir / name;
155
188
  }
156
189
  output_path.parent.mkdir(parents=True, exist_ok=True);
157
- output_path.write_text(combined_js, encoding='utf-8');
190
+ # Add source file header comment for better error messages
191
+ source_header = f"/* Source: {module_path} */\n";
192
+ # Add ES module exports so Vite can resolve named imports between modules
193
+ module_export_names = sorted(set(exports_list + list(non_root_globals.keys())));
194
+ if module_export_names {
195
+ import re;
196
+ # Strip any existing export declarations to avoid duplicates
197
+ clean_combined_js = re.sub(r'\nexport\s*\{[^}]*\}\s*;?\s*', '\n', combined_js);
198
+ clean_combined_js = re.sub(
199
+ r'\bexport\s+(let|const|var|function|class)\b', r'\1', clean_combined_js
200
+ );
201
+ export_stmt = f"\nexport {{ {', '.join(module_export_names)} }};\n";
202
+ combined_js = clean_combined_js + export_stmt;
203
+ }
204
+ output_path.write_text(source_header + combined_js, encoding='utf-8');
158
205
  if (not manifest or not manifest.imports) {
159
206
  return;
160
207
  }
@@ -193,8 +240,29 @@ impl ViteCompiler.compile_runtime_utils(self: ViteCompiler) -> tuple[str, list[s
193
240
  runtimeutils_exports_list = self.jac_compiler.extract_exports(
194
241
  runtimeutils_manifest
195
242
  );
196
- all_exports = sorted(set((runtimeutils_exports_list + self.ROUTER_EXPORTS)));
197
- combined_runtime_utils_js = runtimeutils_js;
243
+ # Include both function exports and glob exports (e.g., useState, useEffect)
244
+ glob_names = list(runtimeutils_manifest.globals) if runtimeutils_manifest else [];
245
+ all_exports = sorted(
246
+ set(runtimeutils_exports_list + self.ROUTER_EXPORTS + glob_names)
247
+ );
248
+ # Strip all existing export statements from compiled JS to avoid duplicates.
249
+ # The codegen produces `export let`, `export function`, `export class`, and
250
+ # `export { ... }` forms. We remove the export keyword from declarations and
251
+ # remove export blocks entirely, then add one comprehensive export at the end.
252
+ import re;
253
+ clean_js = re.sub(r'\nexport\s*\{[^}]*\}\s*;?\s*', '\n', runtimeutils_js);
254
+ clean_js = re.sub(r'\bexport\s+(let|const|var|function|class)\b', r'\1', clean_js);
255
+ # Append single comprehensive ES module export for Vite to resolve @jac/runtime
256
+ export_names = [
257
+ e
258
+ for e in all_exports
259
+ if not e.startswith('_')
260
+ ];
261
+ # Also include internal names that are imported by compiled modules
262
+ internal_exports = ['__jacJsx', '__jacSpawn', '__jacCallFunction'];
263
+ all_export_names = sorted(set(export_names + internal_exports));
264
+ export_stmt = f"\nexport {{ {', '.join(all_export_names)} }};\n";
265
+ combined_runtime_utils_js = clean_js + export_stmt;
198
266
  self.compiled_dir.mkdir(parents=True, exist_ok=True);
199
267
  (self.compiled_dir / 'client_runtime.js').write_text(
200
268
  combined_runtime_utils_js, encoding='utf-8'
@@ -228,24 +296,25 @@ impl ViteCompiler.init(
228
296
  ) ;
229
297
  }
230
298
  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
299
+ # Detect project root: package.json may be in .jac/client/configs/ or project root
232
300
  if (
233
- vite_package_json.parent.name == '.jac-client.configs'
234
- and vite_package_json.parent.parent.name == '.client-build'
301
+ vite_package_json.parent.name == 'configs'
302
+ and vite_package_json.parent.parent.name == 'client'
303
+ and vite_package_json.parent.parent.parent.name == '.jac'
235
304
  ) {
236
- self.project_dir = vite_package_json.parent.parent.parent;
237
- } elif (vite_package_json.parent.name == '.jac-client.configs') {
305
+ # .jac/client/configs/package.json -> go up 3 levels to project root
306
+ self.project_dir = vite_package_json.parent.parent.parent.parent;
307
+ } elif (vite_package_json.parent.name == 'configs') {
238
308
  self.project_dir = vite_package_json.parent.parent;
239
309
  } else {
240
310
  self.project_dir = vite_package_json.parent;
241
311
  }
242
312
  self.runtime_path = runtime_path;
243
- self.compiled_dir = self.project_dir / '.client-build' / 'compiled';
313
+ self.compiled_dir = self._get_client_dir() / 'compiled';
244
314
  self.jac_compiler = JacToJSCompiler(
245
315
  compile_to_js_func, extract_exports_func, extract_globals_func
246
316
  );
247
317
  self.import_processor = ImportProcessor();
248
318
  self.asset_processor = AssetProcessor();
249
- self.babel_processor = BabelProcessor(self.project_dir);
250
319
  self.vite_bundler = ViteBundler(self.project_dir, vite_output_dir, vite_minify);
251
320
  }
@@ -27,6 +27,7 @@ impl JacClientConfig.get_default_config(self: JacClientConfig) -> dict[str, Any]
27
27
  'resolve': {}
28
28
  },
29
29
  'ts': {'compilerOptions': {}, 'include': [], 'exclude': []},
30
+ 'configs': {}, # Generic config file generation: { "postcss": {...}, "tailwind": {...} }
30
31
  'package': {
31
32
  'name': '',
32
33
  'version': '1.0.0',
@@ -61,6 +62,7 @@ impl JacClientConfig.load(self: JacClientConfig) -> dict[str, Any] {
61
62
  user_config: dict[str, Any] = {
62
63
  'vite': client_config.get('vite', {}),
63
64
  'ts': client_config.get('ts', {}),
65
+ 'configs': client_config.get('configs', {}),
64
66
  'package': {
65
67
  'name': jac_config.project.name,
66
68
  'version': jac_config.project.version,
@@ -91,6 +93,12 @@ impl JacClientConfig.get_ts_config(self: JacClientConfig) -> dict[str, Any] {
91
93
  return config.get('ts', {});
92
94
  }
93
95
 
96
+ """Get generic config files to generate (postcss, tailwind, etc.)."""
97
+ impl JacClientConfig.get_configs(self: JacClientConfig) -> dict[str, Any] {
98
+ config = self.load();
99
+ return config.get('configs', {});
100
+ }
101
+
94
102
  """Save configuration back to jac.toml using core JacConfig."""
95
103
  impl JacClientConfig.save(self: JacClientConfig) -> None {
96
104
  jac_config = self.get_jac_config();