jac-client 0.2.8__py3-none-any.whl → 0.2.10__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 (64) hide show
  1. jac_client/examples/all-in-one/{app.jac → main.jac} +5 -5
  2. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +8 -1
  3. jac_client/examples/all-in-one/pages/FeaturesTest.jac +16 -1
  4. jac_client/examples/all-in-one/pages/{FeaturesTest.cl.jac → features_test_ui.cl.jac} +11 -0
  5. jac_client/examples/all-in-one/pages/nestedDemo.jac +1 -1
  6. jac_client/examples/all-in-one/pages/notFound.jac +2 -7
  7. jac_client/plugin/cli.jac +491 -411
  8. jac_client/plugin/client.jac +25 -0
  9. jac_client/plugin/client_runtime.cl.jac +5 -1
  10. jac_client/plugin/impl/client.impl.jac +96 -55
  11. jac_client/plugin/impl/client_runtime.impl.jac +154 -0
  12. jac_client/plugin/plugin_config.jac +243 -15
  13. jac_client/plugin/src/config_loader.jac +1 -0
  14. jac_client/plugin/src/desktop_config.jac +31 -0
  15. jac_client/plugin/src/impl/compiler.impl.jac +1 -1
  16. jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
  17. jac_client/plugin/src/impl/desktop_config.impl.jac +191 -0
  18. jac_client/plugin/src/impl/vite_bundler.impl.jac +97 -16
  19. jac_client/plugin/src/targets/desktop/sidecar/main.py +144 -0
  20. jac_client/plugin/src/targets/desktop_target.jac +37 -0
  21. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +2334 -0
  22. jac_client/plugin/src/targets/impl/registry.impl.jac +64 -0
  23. jac_client/plugin/src/targets/impl/web_target.impl.jac +157 -0
  24. jac_client/plugin/src/targets/register.jac +21 -0
  25. jac_client/plugin/src/targets/registry.jac +87 -0
  26. jac_client/plugin/src/targets/web_target.jac +35 -0
  27. jac_client/plugin/src/vite_bundler.jac +6 -0
  28. jac_client/plugin/utils/__init__.jac +1 -0
  29. jac_client/plugin/utils/impl/node_installer.impl.jac +249 -0
  30. jac_client/plugin/utils/node_installer.jac +41 -0
  31. jac_client/templates/client.jacpack +72 -0
  32. jac_client/templates/fullstack.jacpack +61 -0
  33. jac_client/tests/conftest.py +48 -7
  34. jac_client/tests/test_cli.py +184 -70
  35. jac_client/tests/test_e2e.py +232 -0
  36. jac_client/tests/test_helpers.py +65 -0
  37. jac_client/tests/test_it.py +91 -135
  38. jac_client/tests/test_it_desktop.py +891 -0
  39. {jac_client-0.2.8.dist-info → jac_client-0.2.10.dist-info}/METADATA +4 -4
  40. jac_client-0.2.10.dist-info/RECORD +115 -0
  41. {jac_client-0.2.8.dist-info → jac_client-0.2.10.dist-info}/WHEEL +1 -1
  42. jac_client-0.2.8.dist-info/RECORD +0 -97
  43. /jac_client/examples/all-in-one/pages/{BudgetPlanner.cl.jac → budget_planner_ui.cl.jac} +0 -0
  44. /jac_client/examples/asset-serving/css-with-image/{src/app.jac → main.jac} +0 -0
  45. /jac_client/examples/asset-serving/image-asset/{src/app.jac → main.jac} +0 -0
  46. /jac_client/examples/asset-serving/import-alias/{src/app.jac → main.jac} +0 -0
  47. /jac_client/examples/basic/{src/app.jac → main.jac} +0 -0
  48. /jac_client/examples/basic-auth/{src/app.jac → main.jac} +0 -0
  49. /jac_client/examples/basic-auth-with-router/{src/app.jac → main.jac} +0 -0
  50. /jac_client/examples/basic-full-stack/{src/app.jac → main.jac} +0 -0
  51. /jac_client/examples/css-styling/js-styling/{src/app.jac → main.jac} +0 -0
  52. /jac_client/examples/css-styling/material-ui/{src/app.jac → main.jac} +0 -0
  53. /jac_client/examples/css-styling/pure-css/{src/app.jac → main.jac} +0 -0
  54. /jac_client/examples/css-styling/sass-example/{src/app.jac → main.jac} +0 -0
  55. /jac_client/examples/css-styling/styled-components/{src/app.jac → main.jac} +0 -0
  56. /jac_client/examples/css-styling/tailwind-example/{src/app.jac → main.jac} +0 -0
  57. /jac_client/examples/full-stack-with-auth/{src/app.jac → main.jac} +0 -0
  58. /jac_client/examples/little-x/{src/app.jac → main.jac} +0 -0
  59. /jac_client/examples/nested-folders/nested-advance/{src/app.jac → main.jac} +0 -0
  60. /jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +0 -0
  61. /jac_client/examples/ts-support/{src/app.jac → main.jac} +0 -0
  62. /jac_client/examples/with-router/{src/app.jac → main.jac} +0 -0
  63. {jac_client-0.2.8.dist-info → jac_client-0.2.10.dist-info}/entry_points.txt +0 -0
  64. {jac_client-0.2.8.dist-info → jac_client-0.2.10.dist-info}/top_level.txt +0 -0
jac_client/plugin/cli.jac CHANGED
@@ -1,504 +1,584 @@
1
1
  """Command line interface tool for the Jac Client.
2
2
 
3
- This module extends the core `create` command to add client-side (frontend)
4
- project setup via the --cl flag using the extend_command API.
3
+ This module extends core CLI commands to add the --npm flag for
4
+ client-side (frontend) package management:
5
+ - `jac add --npm`: Add npm dependencies
6
+ - `jac remove --npm`: Remove npm dependencies
7
+
8
+ For creating client projects, use: `jac create --use client`
5
9
  """
6
10
 
7
- import os;
8
- import re;
9
- import sys;
10
- import pathlib;
11
- import subprocess;
12
- import shutil;
13
11
  import from jaclang.cli.registry { get_registry }
14
12
  import from jaclang.cli.command { Arg, ArgKind, HookContext }
15
13
  import from jaclang.pycore.runtime { hookimpl }
16
- import from jaclang.project.config { find_project_root }
14
+ import os;
15
+
16
+ glob registry = get_registry();
17
+
18
+ # Register targets when module loads
19
+ # Import base target classes - this should trigger annex pass to auto-discover .impl.jac files
20
+ import from jac_client.plugin.src.targets.desktop_target {
21
+ DesktopTarget
22
+ }
23
+ import from jac_client.plugin.src.targets.web_target { WebTarget }
24
+ import from jac_client.plugin.src.targets.register { register_targets }
25
+
26
+ with entry {
27
+ register_targets();
28
+ }
29
+
30
+ """Register 'build' command at module level."""
31
+ @registry.command(
32
+ name="build",
33
+ help="Build a Jac application for a specific target",
34
+ args=[
35
+ Arg.create(
36
+ "filename",
37
+ kind=ArgKind.POSITIONAL,
38
+ default="main.jac",
39
+ help="Path to .jac file (default: main.jac)"
40
+ ),
41
+ Arg.create(
42
+ "client",
43
+ typ=str,
44
+ default="web",
45
+ help="Build client (web, desktop)",
46
+ choices=["web", "desktop"],
47
+ short="" # Disable auto-generated -t (may conflict with other plugins)
48
+ ),
49
+ Arg.create(
50
+ "platform",
51
+ typ=str,
52
+ default=None,
53
+ help="Platform for desktop builds (windows, macos, linux, all)",
54
+ choices=["windows", "macos", "linux", "all"],
55
+ short="p" # Use -p for platform
56
+ ),
57
+
58
+ ],
59
+ examples=[
60
+ ("jac build", "Build web target (default)"),
61
+ ("jac build main.jac", "Build specific file"),
62
+ ("jac build --client desktop", "Build desktop app"),
63
+ ("jac build --client desktop --platform windows", "Build for Windows"),
64
+
65
+ ],
66
+ group="build",
67
+ source="jac-client"
68
+ )
69
+ def build(
70
+ filename: str = "main.jac", target: str = "web", platform: str | None = None
71
+ ) -> int {
72
+ # This will be handled by the pre_hook
73
+ return 0;
74
+ }
75
+
76
+ """Register 'setup' command at module level."""
77
+ @registry.command(
78
+ name="setup",
79
+ help="Setup a build target (one-time initialization)",
80
+ args=[
81
+ Arg.create(
82
+ "target", kind=ArgKind.POSITIONAL, help="Target to setup (e.g., desktop)"
83
+ ),
84
+
85
+ ],
86
+ examples=[("jac setup desktop", "Setup desktop target (Tauri)"), ],
87
+ group="project",
88
+ source="jac-client"
89
+ )
90
+ def setup(target: str) -> int {
91
+ # This will be handled by the pre_hook
92
+ return 0;
93
+ }
17
94
 
18
95
  """Jac CLI extensions for client-side development."""
19
96
  class JacCmd {
20
97
  """Create Jac CLI cmds."""
21
98
  @hookimpl
22
99
  static def create_cmd -> None {
23
- """Extend core create command to add --cl flag for client-side setup.""";
100
+ """Extend core commands to add --npm flag for npm package management.""";
24
101
  registry = get_registry();
25
102
 
26
- # Extend the core 'create' command with client-specific arguments
103
+ # Extend the core 'create' command with --skip flag for client template
27
104
  registry.extend_command(
28
105
  command_name="create",
29
106
  args=[
30
107
  Arg.create(
31
- "cl",
108
+ "skip",
32
109
  typ=bool,
33
110
  default=False,
34
- help="Include client-side (frontend) setup",
35
- short="c"
111
+ help="Skip npm package installation (for --use client)"
36
112
  ),
113
+
114
+ ],
115
+ pre_hook=_handle_create_skip,
116
+ source="jac-client"
117
+ );
118
+
119
+ # Extend the core 'add' command with --npm flag
120
+ registry.extend_command(
121
+ command_name="add",
122
+ args=[
37
123
  Arg.create(
38
- "skip",
124
+ "npm",
39
125
  typ=bool,
40
126
  default=False,
41
- help="Skip installing default packages (only for --cl)",
42
- short="s"
127
+ help="Add as npm (client-side) dependency"
43
128
  ),
129
+
130
+ ],
131
+ pre_hook=_handle_npm_add,
132
+ source="jac-client"
133
+ );
134
+
135
+ # Extend the core 'remove' command with --npm flag
136
+ registry.extend_command(
137
+ command_name="remove",
138
+ args=[
44
139
  Arg.create(
45
- "verbose",
140
+ "npm",
46
141
  typ=bool,
47
142
  default=False,
48
- help="Show detailed output during installation",
49
- short="v"
143
+ help="Remove npm (client-side) dependency"
50
144
  ),
51
145
 
52
146
  ],
53
- pre_hook=_handle_client_create,
147
+ pre_hook=_handle_npm_remove,
54
148
  source="jac-client"
55
149
  );
150
+
151
+ # Add pre-hook to build command (command is registered at module level)
152
+ registry.extend_command(
153
+ command_name="build", pre_hook=_handle_build_target, source="jac-client"
154
+ );
155
+
156
+ # Add pre-hook to setup command (command is registered at module level)
157
+ registry.extend_command(
158
+ command_name="setup", pre_hook=_handle_setup_target, source="jac-client"
159
+ );
160
+
161
+ # Extend 'start' command with --client flag
162
+ # Note: jac-scale also uses --client, so we use --client to avoid conflict
163
+ if registry.has_command("start") {
164
+ registry.extend_command(
165
+ command_name="start",
166
+ args=[
167
+ Arg.create(
168
+ "client", # Use client to avoid conflict with jac-scale's --client
169
+ typ=str,
170
+ default="web",
171
+ help="Client build target for dev server (web, desktop)",
172
+ choices=["web", "desktop"],
173
+ short="" # Disable auto-generated short flag
174
+ ),
175
+
176
+ ],
177
+ pre_hook=_handle_start_target,
178
+ source="jac-client"
179
+ );
180
+ }
181
+ }
182
+ }
183
+
184
+ """Pre-hook to handle --skip flag for create command."""
185
+ def _handle_create_skip(ctx: HookContext) -> None {
186
+ # Check if --skip flag is set
187
+ skip_flag = ctx.get_arg("skip", False);
188
+ if skip_flag {
189
+ # Set environment variable to skip npm installation
190
+ os.environ["JAC_CLIENT_SKIP_NPM_INSTALL"] = "1";
56
191
  }
192
+ # Let core create command run normally
57
193
  }
58
194
 
59
- """Pre-hook to handle --cl flag for client-side project creation."""
60
- def _handle_client_create(ctx: HookContext) -> None {
61
- # Check if --cl flag is set
62
- cl_flag = ctx.get_arg("cl", False);
63
- if not cl_flag {
64
- # Let core create command run normally
195
+ """Pre-hook to handle --npm flag for adding npm dependencies."""
196
+ def _handle_npm_add(ctx: HookContext) -> None {
197
+ # Check if --npm flag is set
198
+ npm_flag = ctx.get_arg("npm", False);
199
+ if not npm_flag {
200
+ # Let core add command run normally
65
201
  return;
66
202
  }
67
203
 
68
- # Handle client-side project creation
69
- cwd = pathlib.Path(os.getcwd());
70
- name = ctx.get_arg("name", "main");
71
- force = ctx.get_arg("force", False);
72
- skip_install = ctx.get_arg("skip", False);
73
- verbose = ctx.get_arg("verbose", False);
204
+ import from jaclang.project.config { get_config }
205
+ import from jaclang.project.dep_registry { get_dependency_registry }
206
+ import from jaclang.project.dependencies { DependencyResolver }
207
+ import from jaclang.cli.console { console }
74
208
 
75
- try {
76
- _create_client_project(cwd, name, force, skip_install, verbose);
77
- # Cancel core handler with success code
78
- ctx.set_data("cancel_execution", True);
79
- ctx.set_data("cancel_return_code", 0);
80
- } except SystemExit as e {
81
- # Preserve exit code from sys.exit() calls
82
- ctx.set_data("cancel_execution", True);
83
- ctx.set_data("cancel_return_code", e.code if e.code is not None else 1);
84
- } except Exception as e {
85
- print(f"Error creating client project: {e}", file=sys.stderr);
209
+ config = get_config();
210
+ if config is None {
211
+ console.error(
212
+ "No jac.toml found", hint="Run 'jac create' to create a project."
213
+ );
86
214
  ctx.set_data("cancel_execution", True);
87
215
  ctx.set_data("cancel_return_code", 1);
216
+ return;
88
217
  }
89
- }
90
218
 
91
- """Create a client-side Jac project with organized folder structure."""
92
- def _create_client_project(
93
- cwd: pathlib.Path,
94
- name: str,
95
- force: bool,
96
- skip: bool = False,
97
- verbose: bool = False
98
- ) -> None {
99
- project_name = name or cwd.name;
100
-
101
- if not project_name or project_name == 'main' {
102
- print(
103
- "Error: Project name is required for client projects. Use: jac create --cl <name>",
104
- file=sys.stderr
219
+ registry = get_dependency_registry();
220
+ dep_type = registry.get_by_flag("--npm");
221
+ if dep_type is None {
222
+ console.error(
223
+ "--npm flag requires jac-client plugin",
224
+ hint="Install with: pip install jac-client"
105
225
  );
106
- <>exit(1);
226
+ ctx.set_data("cancel_execution", True);
227
+ ctx.set_data("cancel_return_code", 1);
228
+ return;
107
229
  }
108
230
 
109
- if not re.match('^[a-zA-Z0-9_-]+$', project_name) {
110
- print(
111
- "Error: Project name must contain only letters, numbers, hyphens, and underscores",
112
- file=sys.stderr
113
- );
114
- <>exit(1);
115
- }
231
+ packages = ctx.get_arg("packages", []);
232
+ dev = ctx.get_arg("dev", False);
116
233
 
117
- existing = find_project_root(cwd);
118
- if existing and not force {
119
- (project_root, toml_path) = existing;
120
- print(f"Already in a Jac project: {toml_path}", file=sys.stderr);
121
- print("Use --force to reinitialize.", file=sys.stderr);
122
- <>exit(1);
234
+ # If no packages specified, install all npm packages from jac.toml
235
+ if not packages {
236
+ if dep_type.install_all_handler is None {
237
+ console.error(f"No install_all handler registered for {dep_type.name}");
238
+ ctx.set_data("cancel_execution", True);
239
+ ctx.set_data("cancel_return_code", 1);
240
+ return;
241
+ }
242
+ try {
243
+ console.print(
244
+ f"\n📦 Installing all {dep_type.name} packages from jac.toml",
245
+ style="bold"
246
+ );
247
+ with console.status(
248
+ f"[cyan]Installing {dep_type.name} packages...[/cyan]", spinner="dots"
249
+ ) as status {
250
+ dep_type.install_all_handler(config);
251
+ }
252
+ console.success(f"Installed all {dep_type.name} packages");
253
+ ctx.set_data("cancel_execution", True);
254
+ ctx.set_data("cancel_return_code", 0);
255
+ } except Exception as e {
256
+ console.error(f"Failed to install packages: {e}");
257
+ ctx.set_data("cancel_execution", True);
258
+ ctx.set_data("cancel_return_code", 1);
259
+ }
260
+ return;
123
261
  }
124
262
 
125
- project_path: pathlib.Path;
126
- if name and name != cwd.name {
127
- project_path = cwd / name;
128
- if project_path.exists() and not force {
129
- print(f"Error: Directory '{name}' already exists", file=sys.stderr);
130
- <>exit(1);
263
+ # Install specific packages
264
+ try {
265
+ resolver = DependencyResolver(config=config);
266
+ console.print(
267
+ f"\n📦 Adding {len(packages)} {dep_type.name} package(s)", style="bold"
268
+ );
269
+ for pkg_spec in packages {
270
+ (name, version) = resolver.parse_spec(pkg_spec);
271
+ with console.status(f"[cyan]Installing {name}...[/cyan]", spinner="dots") as status {
272
+ dep_type.install_handler(config, name, version, dev);
273
+ }
274
+ console.print(f" ✔ {name}", style="success");
131
275
  }
132
- project_path.mkdir(parents=True, exist_ok=True);
133
- } else {
134
- project_path = cwd;
276
+ section = f"dependencies.{dep_type.dev_name if dev else dep_type.name}";
277
+ console.print(f"\n ✔ Updated jac.toml [{section}]", style="muted");
278
+ ctx.set_data("cancel_execution", True);
279
+ ctx.set_data("cancel_return_code", 0);
280
+ } except Exception as e {
281
+ console.error(f"Failed to install packages: {e}");
282
+ ctx.set_data("cancel_execution", True);
283
+ ctx.set_data("cancel_return_code", 1);
135
284
  }
285
+ }
136
286
 
137
- print(f"Creating Jac client application: {project_name}");
138
-
139
- components_dir = project_path / "components";
140
- components_dir.mkdir(parents=True, exist_ok=True);
141
-
142
- assets_dir = project_path / "assets";
143
- assets_dir.mkdir(parents=True, exist_ok=True);
144
-
145
- (project_path / ".jac" / "client").mkdir(parents=True, exist_ok=True);
146
-
147
- toml_path = project_path / "jac.toml";
148
- toml_content = f'''[project]
149
- name = "{project_name}"
150
- version = "1.0.0"
151
- description = "Jac client application: {project_name}"
152
- entry-point = "main.jac"
287
+ """Pre-hook to handle --client flag for build command."""
288
+ def _handle_build_target(ctx: HookContext) -> None {
289
+ import from jaclang.cli.console { console }
290
+ import from jac_client.plugin.src.targets.registry { get_target_registry }
291
+ import from pathlib { Path }
292
+ import from jaclang.project.config { get_config }
293
+
294
+ target_name = ctx.get_arg("client", "web");
295
+ platform = ctx.get_arg("platform", None);
296
+
297
+ registry = get_target_registry();
298
+ target = registry.get(target_name);
299
+
300
+ if not target {
301
+ console.error(f"Unknown client target: {target_name}");
302
+ console.print(
303
+ f"Available client targets: {', '.join(
304
+ [t.name for t in registry.get_all()]
305
+ )}",
306
+ style="muted"
307
+ );
308
+ ctx.set_data("cancel_execution", True);
309
+ ctx.set_data("cancel_return_code", 1);
310
+ return;
311
+ }
153
312
 
154
- [dependencies]
313
+ # Store target info in context for use by build handler
314
+ ctx.set_data("build_target", target);
315
+ ctx.set_data("build_platform", platform);
155
316
 
156
- [dependencies.npm]
157
- "jac-client-node" = "1.0.3"
317
+ # If target requires setup, warn user
318
+ if target.requires_setup {
319
+ console.warning(
320
+ f"Client target '{target_name}' requires setup. Run 'jac setup {target_name}' first."
321
+ );
322
+ }
158
323
 
159
- [dependencies.npm.dev]
160
- "@jac-client/dev-deps" = "1.0.0"
324
+ # Build using target's build method
325
+ try {
326
+ config = get_config();
327
+ if not config {
328
+ console.error("No jac.toml found. Run 'jac create' to create a project.");
329
+ ctx.set_data("cancel_execution", True);
330
+ ctx.set_data("cancel_return_code", 1);
331
+ return;
332
+ }
161
333
 
162
- [dev-dependencies]
163
- watchdog = ">=3.0.0"
334
+ entry_file = ctx.get_arg("filename", None);
335
+ if not entry_file {
336
+ entry_file = config.entry_point;
337
+ }
338
+ if not entry_file {
339
+ console.error("No entry file specified. Use: jac build <file>");
340
+ ctx.set_data("cancel_execution", True);
341
+ ctx.set_data("cancel_return_code", 1);
342
+ return;
343
+ }
164
344
 
165
- [serve]
166
- base_route_app = "app"
345
+ entry_path = Path(entry_file);
346
+ project_dir = config.project_root or Path.cwd();
347
+ if not entry_path.is_absolute() {
348
+ entry_path = project_dir / entry_path;
349
+ }
350
+ bundle_path = target.build(entry_path, project_dir, platform);
167
351
 
168
- [plugins.client]
169
- # Vite bundler configuration (optional overrides)
170
- # vite.plugins = []
171
- # vite.build = {{}}
172
- ''';
173
- with open(toml_path, 'w') as f {
174
- f.write(toml_content);
175
- }
176
- print(f"Created {toml_path}");
177
-
178
- main_jac_content = '''"""Main entry point for the Jac client application."""
179
-
180
- # Client-side imports (useState is auto-injected when using `has` variables)
181
- cl import from react { useEffect }
182
- cl import from .components.Button { Button }
183
-
184
- # Client-side component
185
- cl {
186
- def:pub app() -> any {
187
- has count: int = 0;
188
-
189
- useEffect(lambda -> None {
190
- console.log("Count updated:", count);
191
- }, [count]);
192
-
193
- return <div style={{padding: "2rem", fontFamily: "Arial, sans-serif"}}>
194
- <h1>Hello, World!</h1>
195
- <p>Count: {count}</p>
196
- <div style={{display: "flex", gap: "1rem", marginTop: "1rem"}}>
197
- <Button
198
- label="Increment"
199
- onClick={lambda -> None { count = count + 1; }}
200
- variant="primary"
201
- />
202
- <Button
203
- label="Reset"
204
- onClick={lambda -> None { count = 0; }}
205
- variant="secondary"
206
- />
207
- </div>
208
- </div>;
209
- }
210
- }
211
- ''';
212
- with open(project_path / "main.jac", 'w') as f {
213
- f.write(main_jac_content);
352
+ console.success(f"Build complete: {bundle_path}");
353
+ ctx.set_data("cancel_execution", True);
354
+ ctx.set_data("cancel_return_code", 0);
355
+ } except NotImplementedError as e {
356
+ console.error(
357
+ f"Target '{target_name}' is not yet implemented. Coming in Phase 2!"
358
+ );
359
+ ctx.set_data("cancel_execution", True);
360
+ ctx.set_data("cancel_return_code", 1);
361
+ } except Exception as e {
362
+ console.error(f"Build failed: {e}");
363
+ ctx.set_data("cancel_execution", True);
364
+ ctx.set_data("cancel_return_code", 1);
214
365
  }
215
- print("Created main.jac");
216
-
217
- button_cl_jac_content = '''"""Button component for the Jac client application."""
218
-
219
- def:pub Button(label: str, onClick: any, variant: str = "primary", disabled: bool = False) -> any {
220
- base_styles = {
221
- "padding": "0.75rem 1.5rem",
222
- "fontSize": "1rem",
223
- "fontWeight": "600",
224
- "borderRadius": "0.5rem",
225
- "border": "none",
226
- "cursor": "not-allowed" if disabled else "pointer",
227
- "transition": "all 0.2s ease"
228
- };
229
-
230
- variant_styles = {
231
- "primary": {
232
- "backgroundColor": "#9ca3af" if disabled else "#3b82f6",
233
- "color": "#ffffff"
234
- },
235
- "secondary": {
236
- "backgroundColor": "#e5e7eb" if disabled else "#6b7280",
237
- "color": "#ffffff"
238
- }
239
- };
240
-
241
- return <button
242
- style={{**base_styles, **variant_styles[variant]}}
243
- onClick={onClick}
244
- disabled={disabled}
245
- >
246
- {label}
247
- </button>;
248
366
  }
249
- ''';
250
- with open(components_dir / "Button.cl.jac", 'w') as f {
251
- f.write(button_cl_jac_content);
252
- }
253
- print("Created components/Button.cl.jac");
254
-
255
- readme_content = f''' # {project_name}
256
-
257
-
258
- A Jac client-side application with React support.
259
-
260
- ## Project Structure
261
-
262
- ```
263
- {project_name}/
264
- ├── jac.toml # Project configuration
265
- ├── main.jac # Main application entry
266
- ├── components/ # Reusable components
267
- │ └── Button.cl.jac # Example Jac component
268
- ├── assets/ # Static assets (images, fonts, etc.)
269
- └── build/ # Build output (generated)
270
- ```
271
-
272
- ## Getting Started
273
-
274
- Start the development server:
275
-
276
- ```bash
277
- jac start main.jac
278
- ```
279
367
 
280
- ## Components
368
+ """Pre-hook to handle --client flag for start command."""
369
+ def _handle_start_target(ctx: HookContext) -> None {
370
+ import from jaclang.cli.console { console }
371
+ import from jac_client.plugin.src.targets.registry { get_target_registry }
281
372
 
282
- Create Jac components in `components/` as `.cl.jac` files and import them:
373
+ target_name = ctx.get_arg("client", "web");
283
374
 
284
- ```jac
285
- cl import from .components.Button {{ Button }}
286
- ```
375
+ registry = get_target_registry();
376
+ target = registry.get(target_name);
287
377
 
288
- ## Adding Dependencies
289
-
290
- Add npm packages with the --cl flag:
291
-
292
- ```bash
293
- jac add --cl react-router-dom
294
- ```
295
- ''';
296
- with open(project_path / "README.md", 'w') as f {
297
- f.write(readme_content);
298
- }
299
- print("Created README.md");
300
-
301
- _create_gitignore(project_path);
302
-
303
- # Install default packages unless --skip is specified
304
- if not skip {
305
- print("\nInstalling default packages...");
306
- _install_default_packages(project_path, verbose);
307
- }
308
-
309
- print(f"\nProject '{project_name}' created successfully!");
310
- if name and name != cwd.name {
311
- print("\nNext steps:");
312
- print(f" cd {name}");
313
- print(" jac start main.jac");
314
- } else {
315
- print("\nNext steps:");
316
- print(" jac start main.jac");
378
+ if not target {
379
+ console.error(f"Unknown client target: {target_name}");
380
+ console.print(
381
+ f"Available client targets: {', '.join(
382
+ [t.name for t in registry.get_all()]
383
+ )}",
384
+ style="muted"
385
+ );
386
+ ctx.set_data("cancel_execution", True);
387
+ ctx.set_data("cancel_return_code", 1);
388
+ return;
317
389
  }
318
- }
319
390
 
320
- """Create .gitignore file with client-specific entries."""
321
- def _create_gitignore(project_path: pathlib.Path) -> None {
322
- gitignore_entries = [
323
- "# Jac project",
324
- "packages/",
325
- ".jac_cache/",
326
- "*.jbc",
327
- "*.jir",
328
- "__jaccache__/",
329
- "",
330
- "# Python",
331
- "__pycache__/",
332
- "*.py[cod]",
333
- ".venv/",
334
- "venv/",
335
- "",
336
- "# IDE",
337
- ".idea/",
338
- ".vscode/",
339
- "*.swp",
340
- "",
341
- "# Node.js",
342
- "node_modules/",
343
- "",
344
- "# Jac build artifacts",
345
- ".jac/",
346
- "*.session",
347
- "*.session.*"
348
- ];
349
-
350
- gitignore_path = project_path / ".gitignore";
351
- if gitignore_path.exists() {
352
- with open(gitignore_path, "r") as f {
353
- existing_content = f.read();
391
+ # Store target info in context
392
+ ctx.set_data("start_client_target", target);
393
+
394
+ # Handle desktop target
395
+ if target_name == "desktop" {
396
+ import from pathlib { Path }
397
+ import from jaclang.project.config { get_config }
398
+ config = get_config();
399
+ if not config {
400
+ console.error("No jac.toml found. Run 'jac create' to create a project.");
401
+ ctx.set_data("cancel_execution", True);
402
+ ctx.set_data("cancel_return_code", 1);
403
+ return;
354
404
  }
355
- new_entries: list = [];
356
- for entry in gitignore_entries {
357
- if entry and entry not in existing_content {
358
- new_entries.append(entry);
359
- }
405
+ # Get entry file
406
+ entry_file = ctx.get_arg("filename", None);
407
+ if not entry_file {
408
+ entry_file = config.entry_point;
360
409
  }
361
- if new_entries {
362
- with open(gitignore_path, "a") as f {
363
- f.write("\n" + "\n".join(new_entries));
364
- }
365
- print("Updated .gitignore");
410
+ if not entry_file {
411
+ console.error("No entry file specified. Use: jac start <file>");
412
+ ctx.set_data("cancel_execution", True);
413
+ ctx.set_data("cancel_return_code", 1);
414
+ return;
415
+ }
416
+ entry_path = Path(entry_file);
417
+ project_dir = config.project_root or Path.cwd();
418
+ if not entry_path.is_absolute() {
419
+ entry_path = project_dir / entry_path;
420
+ }
421
+ # Check if setup has been run
422
+ tauri_dir = project_dir / "src-tauri";
423
+ if not tauri_dir.exists() {
424
+ console.error("Desktop target not set up. Run 'jac setup desktop' first.");
425
+ ctx.set_data("cancel_execution", True);
426
+ ctx.set_data("cancel_return_code", 1);
427
+ return;
366
428
  }
367
- } else {
368
- with open(gitignore_path, "w") as f {
369
- f.write("\n".join(gitignore_entries) + "\n");
429
+ # Check if --dev flag is set
430
+ dev_mode = ctx.get_arg("dev", False);
431
+ try {
432
+ if dev_mode {
433
+ # Desktop target with --dev: launch Tauri dev mode (hot reload)
434
+ target.dev(entry_path, project_dir);
435
+ } else {
436
+ # Desktop target without --dev: build web bundle and launch Tauri with built bundle
437
+ target.start(entry_path, project_dir);
438
+ }
439
+ ctx.set_data("cancel_execution", True);
440
+ ctx.set_data("cancel_return_code", 0);
441
+ } except Exception as e {
442
+ import traceback;
443
+ console.error(f"Desktop start failed: {e}");
444
+ console.print(traceback.format_exc(), style="muted");
445
+ ctx.set_data("cancel_execution", True);
446
+ ctx.set_data("cancel_return_code", 1);
370
447
  }
371
- print("Created .gitignore");
448
+ return;
372
449
  }
450
+ # Web target: let existing start command handle it
451
+ # (no cancellation, let it run normally)
373
452
  }
374
453
 
375
- """Install default npm packages in .jac/client directory."""
376
- def _install_default_packages(
377
- project_path: pathlib.Path, verbose: bool = False
378
- ) -> None {
379
- import from jac_client.plugin.src.vite_bundler { ViteBundler }
454
+ """Pre-hook to handle setup command."""
455
+ def _handle_setup_target(ctx: HookContext) -> None {
456
+ import from jaclang.cli.console { console }
457
+ import from jac_client.plugin.src.targets.registry { get_target_registry }
458
+ import from pathlib { Path }
459
+ import from jaclang.project.config { get_config }
460
+
461
+ target_name = ctx.get_arg("target", None);
462
+ if not target_name {
463
+ console.error("No target specified. Use: jac setup <target>");
464
+ console.print("Available targets: desktop", style="muted");
465
+ ctx.set_data("cancel_execution", True);
466
+ ctx.set_data("cancel_return_code", 1);
467
+ return;
468
+ }
469
+
470
+ registry = get_target_registry();
471
+ target = registry.get(target_name);
472
+
473
+ if not target {
474
+ console.error(f"Unknown client target: {target_name}");
475
+ console.print(
476
+ f"Available targets: {', '.join([t.name for t in registry.get_all()])}",
477
+ style="muted"
478
+ );
479
+ ctx.set_data("cancel_execution", True);
480
+ ctx.set_data("cancel_return_code", 1);
481
+ return;
482
+ }
380
483
 
484
+ # Setup using target's setup method
381
485
  try {
382
- # Verify jac.toml exists
383
- toml_path = project_path / "jac.toml";
384
- if not toml_path.exists() {
385
- print(
386
- "Warning: jac.toml not found, skipping package installation",
387
- file=sys.stderr
388
- );
486
+ config = get_config();
487
+ if not config {
488
+ console.error("No jac.toml found. Run 'jac create' to create a project.");
489
+ ctx.set_data("cancel_execution", True);
490
+ ctx.set_data("cancel_return_code", 1);
389
491
  return;
390
492
  }
391
493
 
392
- # Get project name from jac.toml or use directory name
393
- project_name = project_path.name;
394
-
395
- # Create ViteBundler instance (it will load config internally)
396
- bundler = ViteBundler(project_path);
397
-
398
- # Generate package.json with default packages (defaults are added automatically)
399
- bundler.create_package_json(project_name=project_name);
400
-
401
- # Ensure .jac/client directory exists
402
- client_dir = bundler._get_client_dir();
403
- client_dir.mkdir(parents=True, exist_ok=True);
404
-
405
- # Copy package.json to .jac/client/ for npm install
406
- configs_package_json = client_dir / 'configs' / 'package.json';
407
- build_package_json = client_dir / 'package.json';
494
+ project_dir = config.project_root or Path.cwd();
495
+ console.print(
496
+ f"Setting up target '{target_name}' in: {project_dir}", style="muted"
497
+ );
498
+ target.setup(project_dir);
408
499
 
409
- if not configs_package_json.exists() {
410
- print(
411
- "Warning: package.json was not generated, skipping package installation",
412
- file=sys.stderr
500
+ # Verify setup completed successfully
501
+ tauri_dir = project_dir / "src-tauri";
502
+ if not tauri_dir.exists() {
503
+ console.error(
504
+ f"Setup completed but src-tauri directory not found at: {tauri_dir}"
505
+ );
506
+ console.print(
507
+ "This may indicate the setup function failed silently.", style="muted"
413
508
  );
509
+ ctx.set_data("cancel_execution", True);
510
+ ctx.set_data("cancel_return_code", 1);
414
511
  return;
415
512
  }
416
513
 
417
- # Always copy the generated package.json to .jac/client/ for npm install
418
- shutil.copy2(configs_package_json, build_package_json);
514
+ console.success(f"Target '{target_name}' setup complete!");
515
+ ctx.set_data("cancel_execution", True);
516
+ ctx.set_data("cancel_return_code", 0);
517
+ } except NotImplementedError as e {
518
+ console.error(f"Target '{target_name}' setup is not yet implemented.");
519
+ ctx.set_data("cancel_execution", True);
520
+ ctx.set_data("cancel_return_code", 1);
521
+ } except Exception as e {
522
+ import traceback;
523
+ console.error(f"Setup failed: {e}");
524
+ console.print(traceback.format_exc(), style="muted");
525
+ ctx.set_data("cancel_execution", True);
526
+ ctx.set_data("cancel_return_code", 1);
527
+ }
528
+ }
419
529
 
420
- # Read package data for verbose output
421
- import json;
422
- with open(configs_package_json, 'r') as f {
423
- pkg_data = json.load(f);
424
- }
425
- deps = pkg_data.get('dependencies', {});
426
- dev_deps = pkg_data.get('devDependencies', {});
427
-
428
- if verbose {
429
- # Verbose mode: show detailed package list and stream npm output
430
- if deps {
431
- print(" Dependencies:");
432
- for (name, version) in deps.items() {
433
- print(f" - {name}@{version}");
434
- }
435
- }
436
- if dev_deps {
437
- print(" Dev dependencies:");
438
- for (name, version) in dev_deps.items() {
439
- print(f" - {name}@{version}");
440
- }
441
- }
442
- print("\nRunning npm install...");
443
- }
530
+ """Pre-hook to handle --npm flag for removing npm dependencies."""
531
+ def _handle_npm_remove(ctx: HookContext) -> None {
532
+ # Check if --npm flag is set
533
+ npm_flag = ctx.get_arg("npm", False);
534
+ if not npm_flag {
535
+ # Let core remove command run normally
536
+ return;
537
+ }
444
538
 
445
- # Run npm install in .jac/client/ directory
446
- try {
447
- if verbose {
448
- # Stream output for visibility in verbose mode
449
- subprocess.run(
450
- ['npm', 'install', '--progress'], cwd=client_dir, check=True
451
- );
452
- } else {
453
- # Quiet mode: capture output
454
- subprocess.run(
455
- ['npm', 'install'],
456
- cwd=client_dir,
457
- check=True,
458
- capture_output=True,
459
- text=True
460
- );
461
- }
539
+ import from jaclang.project.config { get_config }
540
+ import from jaclang.project.dep_registry { get_dependency_registry }
541
+ import from jaclang.cli.console { console }
462
542
 
463
- # Move package-lock.json to configs/ if it was created
464
- build_package_lock = client_dir / 'package-lock.json';
465
- configs_dir = client_dir / 'configs';
466
- configs_package_lock = configs_dir / 'package-lock.json';
467
- if build_package_lock.exists() {
468
- configs_dir.mkdir(parents=True, exist_ok=True);
469
- if configs_package_lock.exists() {
470
- configs_package_lock.unlink();
471
- }
472
- shutil.move(str(build_package_lock), str(configs_package_lock));
473
- }
543
+ config = get_config();
544
+ if config is None {
545
+ console.error("No jac.toml found. Run 'jac create' to create a project.");
546
+ ctx.set_data("cancel_execution", True);
547
+ ctx.set_data("cancel_return_code", 1);
548
+ return;
549
+ }
474
550
 
475
- print("Default packages installed successfully");
476
- } except subprocess.CalledProcessError as e {
477
- if verbose {
478
- print(
479
- f"Warning: Failed to install packages (exit code {e.returncode})",
480
- file=sys.stderr
481
- );
482
- } else {
483
- print(
484
- f"Warning: Failed to install packages: {e.stderr}", file=sys.stderr
485
- );
486
- }
487
- print("You can install packages later with: jac add --cl", file=sys.stderr);
488
- } except FileNotFoundError {
489
- print(
490
- "Warning: npm command not found. Install Node.js and npm to install packages.",
491
- file=sys.stderr
492
- );
493
- print("You can install packages later with: jac add --cl", file=sys.stderr);
494
- } finally {
495
- # Clean up temporary package.json in .jac/client/
496
- if build_package_json.exists() {
497
- build_package_json.unlink();
498
- }
551
+ packages = ctx.get_arg("packages", []);
552
+ if not packages {
553
+ console.error("No packages specified.");
554
+ ctx.set_data("cancel_execution", True);
555
+ ctx.set_data("cancel_return_code", 1);
556
+ return;
557
+ }
558
+
559
+ registry = get_dependency_registry();
560
+ dep_type = registry.get_by_flag("--npm");
561
+ if dep_type is None {
562
+ console.error("--npm flag requires jac-client plugin to be installed.");
563
+ console.print("Install with: pip install jac-client", style="muted");
564
+ ctx.set_data("cancel_execution", True);
565
+ ctx.set_data("cancel_return_code", 1);
566
+ return;
567
+ }
568
+
569
+ dev = ctx.get_arg("dev", False);
570
+
571
+ try {
572
+ for name in packages {
573
+ console.info(f"Removing {name} ({dep_type.name})...");
574
+ dep_type.remove_handler(config, name, dev);
499
575
  }
576
+ console.success(f"Removed {len(packages)} package(s)");
577
+ ctx.set_data("cancel_execution", True);
578
+ ctx.set_data("cancel_return_code", 0);
500
579
  } except Exception as e {
501
- print(f"Warning: Could not install default packages: {e}", file=sys.stderr);
502
- print("You can install packages later with: jac add --cl", file=sys.stderr);
580
+ console.error(f"Error removing packages: {e}");
581
+ ctx.set_data("cancel_execution", True);
582
+ ctx.set_data("cancel_return_code", 1);
503
583
  }
504
584
  }