jac-client 0.2.7__py3-none-any.whl → 0.2.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. jac_client/examples/all-in-one/{src/app.jac → main.jac} +5 -5
  2. jac_client/examples/all-in-one/{src/pages → pages}/BudgetPlanner.jac +8 -1
  3. jac_client/examples/all-in-one/{src/pages → pages}/FeaturesTest.jac +16 -1
  4. jac_client/examples/all-in-one/{src/pages/FeaturesTest.cl.jac → pages/features_test_ui.cl.jac} +11 -0
  5. jac_client/examples/all-in-one/{src/pages → pages}/nestedDemo.jac +1 -1
  6. jac_client/examples/all-in-one/{src/pages → pages}/notFound.jac +2 -7
  7. jac_client/plugin/cli.jac +162 -430
  8. jac_client/plugin/client.jac +30 -12
  9. jac_client/plugin/client_runtime.cl.jac +19 -15
  10. jac_client/plugin/impl/client.impl.jac +107 -69
  11. jac_client/plugin/impl/client_runtime.impl.jac +181 -9
  12. jac_client/plugin/plugin_config.jac +243 -15
  13. jac_client/plugin/src/config_loader.jac +1 -0
  14. jac_client/plugin/src/impl/compiler.impl.jac +2 -4
  15. jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
  16. jac_client/plugin/src/impl/vite_bundler.impl.jac +241 -11
  17. jac_client/plugin/src/vite_bundler.jac +14 -1
  18. jac_client/plugin/utils/__init__.jac +1 -0
  19. jac_client/plugin/utils/impl/node_installer.impl.jac +249 -0
  20. jac_client/plugin/utils/node_installer.jac +41 -0
  21. jac_client/templates/client.jacpack +72 -0
  22. jac_client/templates/fullstack.jacpack +61 -0
  23. jac_client/tests/conftest.py +48 -7
  24. jac_client/tests/test_cli.py +189 -73
  25. jac_client/tests/test_e2e.py +232 -0
  26. jac_client/tests/test_helpers.py +65 -0
  27. jac_client/tests/test_it.py +97 -137
  28. {jac_client-0.2.7.dist-info → jac_client-0.2.9.dist-info}/METADATA +4 -4
  29. jac_client-0.2.9.dist-info/RECORD +104 -0
  30. {jac_client-0.2.7.dist-info → jac_client-0.2.9.dist-info}/WHEEL +1 -1
  31. jac_client-0.2.7.dist-info/RECORD +0 -97
  32. /jac_client/examples/all-in-one/{src/button.jac → button.jac} +0 -0
  33. /jac_client/examples/all-in-one/{src/components → components}/CategoryFilter.jac +0 -0
  34. /jac_client/examples/all-in-one/{src/components → components}/Header.jac +0 -0
  35. /jac_client/examples/all-in-one/{src/components → components}/ProfitOverview.jac +0 -0
  36. /jac_client/examples/all-in-one/{src/components → components}/Summary.jac +0 -0
  37. /jac_client/examples/all-in-one/{src/components → components}/TransactionForm.jac +0 -0
  38. /jac_client/examples/all-in-one/{src/components → components}/TransactionItem.jac +0 -0
  39. /jac_client/examples/all-in-one/{src/components → components}/TransactionList.jac +0 -0
  40. /jac_client/examples/all-in-one/{src/components → components}/button.jac +0 -0
  41. /jac_client/examples/all-in-one/{src/components → components}/navigation.jac +0 -0
  42. /jac_client/examples/all-in-one/{src/constants → constants}/categories.jac +0 -0
  43. /jac_client/examples/all-in-one/{src/constants → constants}/clients.jac +0 -0
  44. /jac_client/examples/all-in-one/{src/context → context}/BudgetContext.jac +0 -0
  45. /jac_client/examples/all-in-one/{src/hooks → hooks}/useBudget.jac +0 -0
  46. /jac_client/examples/all-in-one/{src/hooks → hooks}/useLocalStorage.jac +0 -0
  47. /jac_client/examples/all-in-one/{src/pages → pages}/LandingPage.jac +0 -0
  48. /jac_client/examples/all-in-one/{src/pages/BudgetPlanner.cl.jac → pages/budget_planner_ui.cl.jac} +0 -0
  49. /jac_client/examples/all-in-one/{src/pages → pages}/loginPage.jac +0 -0
  50. /jac_client/examples/all-in-one/{src/pages → pages}/signupPage.jac +0 -0
  51. /jac_client/examples/all-in-one/{src/utils → utils}/formatters.jac +0 -0
  52. /jac_client/examples/asset-serving/css-with-image/{src/app.jac → main.jac} +0 -0
  53. /jac_client/examples/asset-serving/image-asset/{src/app.jac → main.jac} +0 -0
  54. /jac_client/examples/asset-serving/import-alias/{src/app.jac → main.jac} +0 -0
  55. /jac_client/examples/basic/{src/app.jac → main.jac} +0 -0
  56. /jac_client/examples/basic-auth/{src/app.jac → main.jac} +0 -0
  57. /jac_client/examples/basic-auth-with-router/{src/app.jac → main.jac} +0 -0
  58. /jac_client/examples/basic-full-stack/{src/app.jac → main.jac} +0 -0
  59. /jac_client/examples/css-styling/js-styling/{src/app.jac → main.jac} +0 -0
  60. /jac_client/examples/css-styling/material-ui/{src/app.jac → main.jac} +0 -0
  61. /jac_client/examples/css-styling/pure-css/{src/app.jac → main.jac} +0 -0
  62. /jac_client/examples/css-styling/sass-example/{src/app.jac → main.jac} +0 -0
  63. /jac_client/examples/css-styling/styled-components/{src/app.jac → main.jac} +0 -0
  64. /jac_client/examples/css-styling/tailwind-example/{src/app.jac → main.jac} +0 -0
  65. /jac_client/examples/full-stack-with-auth/{src/app.jac → main.jac} +0 -0
  66. /jac_client/examples/little-x/{src/app.jac → main.jac} +0 -0
  67. /jac_client/examples/nested-folders/nested-advance/{src/app.jac → main.jac} +0 -0
  68. /jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +0 -0
  69. /jac_client/examples/ts-support/{src/app.jac → main.jac} +0 -0
  70. /jac_client/examples/with-router/{src/app.jac → main.jac} +0 -0
  71. {jac_client-0.2.7.dist-info → jac_client-0.2.9.dist-info}/entry_points.txt +0 -0
  72. {jac_client-0.2.7.dist-info → jac_client-0.2.9.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,222 @@ class JacClientPluginConfig {
79
87
  return {
80
88
  "name": "npm",
81
89
  "dev_name": "npm.dev",
82
- "cli_flag": "--cl",
90
+ "cli_flag": "--npm",
83
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
+ }
155
+ }
156
+
157
+ # ===============================================================================
158
+ # Post-create Hook for Client Template
159
+ # ===============================================================================
160
+ """Post-create hook to ensure Node.js is installed and optionally install npm 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 jac_client.plugin.utils.node_installer { NodeInstaller }
164
+ import from jaclang.cli.console { console }
165
+ import shutil;
166
+
167
+ # First, ensure Node.js is installed
168
+ console.print();
169
+ with console.status(
170
+ "[cyan]Checking Node.js installation...[/cyan]", spinner="dots"
171
+ ) as status {
172
+ node_result = NodeInstaller.ensure_node_installed(interactive=True);
173
+ node_installed = node_result[0];
174
+ message = node_result[1];
175
+ was_just_installed = node_result[2];
176
+ }
177
+
178
+ if not node_installed {
179
+ console.warning(f"Node.js is required for client development");
180
+ console.print(f"\n {message}", file=sys.stderr);
181
+ console.print(
182
+ " After installing Node.js, run: jac add --cl\n",
183
+ style="muted",
184
+ file=sys.stderr
185
+ );
186
+ return;
187
+ }
188
+
189
+ console.print(f" ✔ {message}", style="success");
190
+
191
+ # Check if npm package installation should be skipped
192
+ if os.environ.get("JAC_CLIENT_SKIP_NPM_INSTALL") {
193
+ console.print(
194
+ "\n ⏭ Skipping npm package installation (JAC_CLIENT_SKIP_NPM_INSTALL is set)",
195
+ style="muted"
196
+ );
197
+ console.print(
198
+ " Run 'jac add --npm' when ready to install packages.\n", style="muted"
199
+ );
200
+ return;
201
+ }
202
+
203
+ console.print();
204
+
205
+ try {
206
+ # Verify jac.toml exists
207
+ toml_path = project_path / "jac.toml";
208
+ if not toml_path.exists() {
209
+ print(
210
+ "Warning: jac.toml not found, skipping package installation",
211
+ file=sys.stderr
212
+ );
213
+ return;
214
+ }
215
+
216
+ # Create ViteBundler instance
217
+ bundler = ViteBundler(project_path);
218
+
219
+ # Generate package.json with default packages
220
+ bundler.create_package_json(project_name=project_name);
221
+
222
+ # Ensure .jac/client directory exists
223
+ client_dir = bundler._get_client_dir();
224
+ client_dir.mkdir(parents=True, exist_ok=True);
225
+
226
+ # Copy package.json to .jac/client/ for npm install
227
+ configs_package_json = client_dir / 'configs' / 'package.json';
228
+ build_package_json = client_dir / 'package.json';
229
+
230
+ if not configs_package_json.exists() {
231
+ print(
232
+ "Warning: package.json was not generated, skipping package installation",
233
+ file=sys.stderr
234
+ );
235
+ return;
236
+ }
237
+
238
+ shutil.copy2(configs_package_json, build_package_json);
239
+
240
+ # Run npm install using NodeInstaller to handle NVM environment
241
+ try {
242
+ console.print(" Installing npm packages...", style="cyan");
243
+ with console.status(
244
+ "[cyan]Installing npm packages...[/cyan]", spinner="dots"
245
+ ) as status {
246
+ NodeInstaller.run_npm_with_nvm(
247
+ ['install'], cwd=client_dir, timeout=300
248
+ );
249
+ }
250
+
251
+ # Move package-lock.json to configs/ if it was created
252
+ build_package_lock = client_dir / 'package-lock.json';
253
+ configs_package_lock = client_dir / 'configs' / 'package-lock.json';
254
+ if build_package_lock.exists() {
255
+ if configs_package_lock.exists() {
256
+ configs_package_lock.unlink();
257
+ }
258
+ shutil.move(str(build_package_lock), str(configs_package_lock));
259
+ }
260
+
261
+ console.print(" ✔ npm packages installed", style="success");
262
+ } except subprocess.CalledProcessError as e {
263
+ console.warning(f"Failed to install packages: {e.stderr}");
264
+ console.print(
265
+ " 💡 You can install packages later with: jac add --cl\n",
266
+ style="muted"
267
+ );
268
+ } except FileNotFoundError {
269
+ console.warning(
270
+ "npm command not found. This may require reloading your shell."
271
+ );
272
+ console.print(
273
+ " 💡 You can install packages later with: jac add --cl\n",
274
+ style="muted"
275
+ );
276
+ } finally {
277
+ # Clean up temporary package.json
278
+ if build_package_json.exists() {
279
+ build_package_json.unlink();
280
+ }
281
+ }
282
+ } except Exception as e {
283
+ console.warning(f"Could not install default packages: {e}");
284
+ console.print(
285
+ " 💡 You can install packages later with: jac add --cl\n", style="muted"
286
+ );
287
+ }
288
+
289
+ # If Node.js was just installed, offer to restart terminal for full integration
290
+ if was_just_installed {
291
+ console.print();
292
+ console.info("Node.js was just installed via NVM");
293
+ console.print(
294
+ "\n For Node.js to be available in your current shell:", style="muted"
295
+ );
296
+ console.print(" 1. Run: source ~/.nvm/nvm.sh", style="muted");
297
+ console.print(" 2. Or restart your terminal", style="muted");
298
+ console.print("\n 💡 npm commands from jac work immediately", style="info");
299
+ console.print();
300
+ }
89
301
  }
90
302
 
303
+ # ===============================================================================
304
+ # NPM Dependency Handlers
305
+ # ===============================================================================
91
306
  """Install an npm package."""
92
307
  def _npm_install_handler(
93
308
  config: JacConfig,
@@ -101,15 +316,13 @@ def _npm_install_handler(
101
316
  raise RuntimeError("No project root found") ;
102
317
  }
103
318
 
104
- project_dir = config.project_root;
105
-
106
319
  # Add to config (core JacConfig handles this)
107
- package_version = version or "latest";
320
+ package_version: str = version or "latest";
108
321
  config.add_dependency(package_name, package_version, dev=is_dev, dep_type="npm");
109
322
  config.save();
110
323
 
111
324
  # Generate package.json and run npm install
112
- _regenerate_and_install(project_dir);
325
+ _regenerate_and_install(Path(str(config.project_root)));
113
326
  }
114
327
 
115
328
  """Install all npm packages from jac.toml."""
@@ -118,7 +331,7 @@ def _npm_install_all_handler(config: JacConfig) -> None {
118
331
  raise RuntimeError("No project root found") ;
119
332
  }
120
333
 
121
- _regenerate_and_install(config.project_root);
334
+ _regenerate_and_install(Path(str(config.project_root)));
122
335
  }
123
336
 
124
337
  """Remove an npm package."""
@@ -129,7 +342,7 @@ def _npm_remove_handler(
129
342
  raise RuntimeError("No project root found") ;
130
343
  }
131
344
 
132
- project_dir = config.project_root;
345
+ project_dir: Path = Path(str(config.project_root));
133
346
 
134
347
  # Remove from config (core JacConfig handles this)
135
348
  result = config.remove_dependency(package_name, dev=is_dev, dep_type="npm");
@@ -147,8 +360,28 @@ def _npm_remove_handler(
147
360
  """Regenerate package.json from jac.toml and run npm install."""
148
361
  def _regenerate_and_install(project_dir: Path) -> None {
149
362
  import from jac_client.plugin.src.vite_bundler { ViteBundler }
363
+ import from jac_client.plugin.utils.node_installer { NodeInstaller }
150
364
  import shutil;
151
365
 
366
+ # Ensure Node.js is installed before proceeding
367
+ node_result = NodeInstaller.ensure_node_installed(interactive=True);
368
+ node_installed = node_result[0];
369
+ message = node_result[1];
370
+ was_just_installed = node_result[2];
371
+
372
+ if not node_installed {
373
+ raise RuntimeError(
374
+ f"Node.js installation required:\n{message}\n" + "Please install Node.js and try again."
375
+ ) ;
376
+ }
377
+
378
+ # Inform user if Node.js was just installed
379
+ if was_just_installed {
380
+ print(
381
+ "\nNote: Node.js was just installed. npm commands will work via NVM sourcing."
382
+ );
383
+ }
384
+
152
385
  bundler = ViteBundler(project_dir);
153
386
  bundler.create_package_json();
154
387
 
@@ -164,18 +397,13 @@ def _regenerate_and_install(project_dir: Path) -> None {
164
397
  }
165
398
 
166
399
  try {
167
- subprocess.run(
168
- ["npm", "install"],
169
- cwd=client_dir,
170
- check=True,
171
- capture_output=True,
172
- text=True
173
- );
400
+ # Use NodeInstaller.run_npm_with_nvm to handle NVM environment
401
+ NodeInstaller.run_npm_with_nvm(["install"], cwd=client_dir, timeout=300);
174
402
  } except subprocess.CalledProcessError as e {
175
403
  raise RuntimeError(f"Failed to install npm packages: {e.stderr}") from e ;
176
404
  } except FileNotFoundError {
177
405
  raise RuntimeError(
178
- "npm command not found. Ensure Node.js and npm are installed."
406
+ "npm command not found. This may require reloading your shell after Node.js installation."
179
407
  ) from None ;
180
408
  } finally {
181
409
  # Clean up temporary package.json
@@ -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
@@ -7,9 +7,7 @@ impl ViteCompiler._get_client_dir(self: ViteCompiler) -> Path {
7
7
  if config is not None {
8
8
  return config.get_client_dir();
9
9
  }
10
- } except ImportError {
11
- pass;
12
- }
10
+ } except ImportError { }
13
11
  # Fallback to default
14
12
  return self.project_dir / '.jac' / 'client';
15
13
  }
@@ -51,7 +49,7 @@ impl ViteCompiler.create_entry_file(self: ViteCompiler, module_path: Path) -> No
51
49
  entry_file = self.compiled_dir / '_entry.js';
52
50
  # Derive the app module filename from the entry point (e.g., main.jac -> main.js, app.jac -> app.js)
53
51
  app_module_name = module_path.stem;
54
- 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(<App />);\n';
52
+ 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-client/utils";\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';
55
53
  entry_file.write_text(entry_content, encoding='utf-8');
56
54
  }
57
55
 
@@ -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();