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
jac_client/plugin/cli.jac CHANGED
@@ -1,547 +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.
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
- import from jaclang.cli.cmdreg { cmd_registry, CommandPriority }
11
+ import from jaclang.cli.registry { get_registry }
12
+ import from jaclang.cli.command { Arg, ArgKind, HookContext }
14
13
  import from jaclang.pycore.runtime { hookimpl }
15
- import from jaclang.project.config { JacConfig, 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
+ }
16
94
 
17
95
  """Jac CLI extensions for client-side development."""
18
96
  class JacCmd {
19
97
  """Create Jac CLI cmds."""
20
98
  @hookimpl
21
99
  static def create_cmd -> None {
22
- """Override core create command to add --cl flag for client-side setup.""";
23
-
24
- def create(
25
- name: str = 'main',
26
- force: bool = False,
27
- cl: bool = False,
28
- skip: bool = False,
29
- verbose: bool = False
30
- ) -> None {
31
- """Initialize a new Jac project.
32
-
33
- Creates a jac.toml configuration file and basic project structure.
34
- Use --cl to include client-side (frontend) setup with Vite bundling.
35
-
36
- Args:
37
- name: Project name (default: 'main')
38
- force: Overwrite existing jac.toml if present
39
- cl: Include client-side (frontend) setup
40
- skip: Skip installing default packages (only for --cl projects)
41
- verbose: Show detailed output during package installation
42
-
43
- Examples:
44
- jac create
45
- jac create myapp
46
- jac create --cl myapp
47
- jac create --cl --skip myapp
48
- jac create --cl --verbose myapp
49
- jac create --force
50
- """;
51
- cwd = pathlib.Path(os.getcwd());
52
-
53
- if cl {
54
- _create_client_project(cwd, name, force, skip, verbose);
55
- } else {
56
- _create_core_project(cwd, name, force);
57
- }
58
- }
100
+ """Extend core commands to add --npm flag for npm package management.""";
101
+ registry = get_registry();
102
+
103
+ # Extend the core 'create' command with --skip flag for client template
104
+ registry.extend_command(
105
+ command_name="create",
106
+ args=[
107
+ Arg.create(
108
+ "skip",
109
+ typ=bool,
110
+ default=False,
111
+ help="Skip npm package installation (for --use client)"
112
+ ),
113
+
114
+ ],
115
+ pre_hook=_handle_create_skip,
116
+ source="jac-client"
117
+ );
59
118
 
60
- # Register with PLUGIN priority to override core's create command
61
- cmd_registry.register(
62
- create, priority=CommandPriority.PLUGIN, source="jac-client"
119
+ # Extend the core 'add' command with --npm flag
120
+ registry.extend_command(
121
+ command_name="add",
122
+ args=[
123
+ Arg.create(
124
+ "npm",
125
+ typ=bool,
126
+ default=False,
127
+ help="Add as npm (client-side) dependency"
128
+ ),
129
+
130
+ ],
131
+ pre_hook=_handle_npm_add,
132
+ source="jac-client"
63
133
  );
64
- }
65
- }
66
134
 
67
- """Create a standard Jac project (core behavior)."""
68
- def _create_core_project(cwd: pathlib.Path, name: str, force: bool) -> None {
69
- existing = find_project_root(cwd);
70
- if existing and not force {
71
- (project_root, toml_path) = existing;
72
- print(f"Already in a Jac project: {toml_path}", file=sys.stderr);
73
- print("Use --force to reinitialize.", file=sys.stderr);
74
- <>exit(1);
75
- }
135
+ # Extend the core 'remove' command with --npm flag
136
+ registry.extend_command(
137
+ command_name="remove",
138
+ args=[
139
+ Arg.create(
140
+ "npm",
141
+ typ=bool,
142
+ default=False,
143
+ help="Remove npm (client-side) dependency"
144
+ ),
145
+
146
+ ],
147
+ pre_hook=_handle_npm_remove,
148
+ source="jac-client"
149
+ );
76
150
 
77
- project_name = name or cwd.name;
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
+ );
78
155
 
79
- toml_path = cwd / "jac.toml";
80
- if toml_path.exists() and not force {
81
- print("jac.toml already exists. Use --force to overwrite.", file=sys.stderr);
82
- <>exit(1);
83
- }
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
+ );
84
160
 
85
- toml_content = JacConfig.create_default_toml(project_name);
86
- with open(toml_path, "w") as f {
87
- f.write(toml_content);
88
- }
89
- print(f"Created {toml_path}");
90
-
91
- main_jac = cwd / f"{name}.jac";
92
- if not main_jac.exists() {
93
- with open(main_jac, "w") as f {
94
- f.write(
95
- f'''"""Main entry point for {project_name}."""
96
-
97
- with entry {{
98
- print("Hello from {project_name}!");
99
- }}
100
- '''
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"
101
179
  );
102
180
  }
103
- print(f"Created {main_jac}");
104
181
  }
182
+ }
105
183
 
106
- packages_dir = cwd / "packages";
107
- if not packages_dir.exists() {
108
- packages_dir.mkdir(parents=True, exist_ok=True);
109
- print(f"Created {packages_dir}/");
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";
110
191
  }
192
+ # Let core create command run normally
193
+ }
111
194
 
112
- _create_gitignore(cwd, client=False);
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
201
+ return;
202
+ }
113
203
 
114
- print(f"\nProject '{project_name}' initialized successfully!");
115
- print("\nNext steps:");
116
- print(" jac run main.jac # Run the main entry point");
117
- print(" jac add <package> # Add dependencies");
118
- print(" jac install # Install all dependencies");
119
- }
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 }
120
208
 
121
- """Create a client-side Jac project with organized folder structure."""
122
- def _create_client_project(
123
- cwd: pathlib.Path,
124
- name: str,
125
- force: bool,
126
- skip: bool = False,
127
- verbose: bool = False
128
- ) -> None {
129
- project_name = name or cwd.name;
130
-
131
- if not project_name or project_name == 'main' {
132
- print(
133
- "Error: Project name is required for client projects. Use: jac create --cl <name>",
134
- 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."
135
213
  );
136
- <>exit(1);
214
+ ctx.set_data("cancel_execution", True);
215
+ ctx.set_data("cancel_return_code", 1);
216
+ return;
137
217
  }
138
218
 
139
- if not re.match('^[a-zA-Z0-9_-]+$', project_name) {
140
- print(
141
- "Error: Project name must contain only letters, numbers, hyphens, and underscores",
142
- 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"
143
225
  );
144
- <>exit(1);
226
+ ctx.set_data("cancel_execution", True);
227
+ ctx.set_data("cancel_return_code", 1);
228
+ return;
145
229
  }
146
230
 
147
- existing = find_project_root(cwd);
148
- if existing and not force {
149
- (project_root, toml_path) = existing;
150
- print(f"Already in a Jac project: {toml_path}", file=sys.stderr);
151
- print("Use --force to reinitialize.", file=sys.stderr);
152
- <>exit(1);
153
- }
231
+ packages = ctx.get_arg("packages", []);
232
+ dev = ctx.get_arg("dev", False);
154
233
 
155
- project_path: pathlib.Path;
156
- if name and name != cwd.name {
157
- project_path = cwd / name;
158
- if project_path.exists() and not force {
159
- print(f"Error: Directory '{name}' already exists", file=sys.stderr);
160
- <>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;
161
241
  }
162
- project_path.mkdir(parents=True, exist_ok=True);
163
- } else {
164
- project_path = cwd;
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;
165
261
  }
166
262
 
167
- print(f"Creating Jac client application: {project_name}");
168
-
169
- src_dir = project_path / "src";
170
- src_dir.mkdir(parents=True, exist_ok=True);
171
-
172
- components_dir = src_dir / "components";
173
- components_dir.mkdir(parents=True, exist_ok=True);
174
-
175
- assets_dir = project_path / "assets";
176
- assets_dir.mkdir(parents=True, exist_ok=True);
177
-
178
- (project_path / ".client-build").mkdir(parents=True, exist_ok=True);
179
-
180
- toml_path = project_path / "jac.toml";
181
- toml_content = f'''[project]
182
- name = "{project_name}"
183
- version = "1.0.0"
184
- description = "Jac client application: {project_name}"
185
- entry-point = "src/app.jac"
186
-
187
- [plugins.client]
188
- # Vite bundler configuration (optional overrides)
189
- # vite.plugins = []
190
- # vite.build = {{}}
191
- ''';
192
- with open(toml_path, 'w') as f {
193
- f.write(toml_content);
194
- }
195
- print(f"Created {toml_path}");
196
-
197
- app_jac_content = '''"""Main entry point for the Jac client application."""
198
-
199
- # Client-side imports
200
- cl import from react { useState, useEffect }
201
- cl import from ".components/Button.tsx" { Button }
202
-
203
- # Client-side component
204
- cl {
205
- def:pub app() -> any {
206
- [count, setCount] = useState(0);
207
-
208
- useEffect(lambda -> None {
209
- console.log("Count updated:", count);
210
- }, [count]);
211
-
212
- return <div style={{padding: "2rem", fontFamily: "Arial, sans-serif"}}>
213
- <h1>Hello, World!</h1>
214
- <p>Count: {count}</p>
215
- <div style={{display: "flex", gap: "1rem", marginTop: "1rem"}}>
216
- <Button
217
- label="Increment"
218
- onClick={lambda -> None { setCount(count + 1); }}
219
- variant="primary"
220
- />
221
- <Button
222
- label="Reset"
223
- onClick={lambda -> None { setCount(0); }}
224
- variant="secondary"
225
- />
226
- </div>
227
- </div>;
228
- }
229
- }
230
- ''';
231
- with open(src_dir / "app.jac", 'w') as f {
232
- f.write(app_jac_content);
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");
275
+ }
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);
233
284
  }
234
- print("Created src/app.jac");
235
-
236
- button_tsx_content = '''import React from 'react';
237
-
238
- interface ButtonProps {
239
- label: string;
240
- onClick?: () => void;
241
- variant?: 'primary' | 'secondary';
242
- disabled?: boolean;
243
285
  }
244
286
 
245
- export const Button: React.FC<ButtonProps> = ({
246
- label,
247
- onClick,
248
- variant = 'primary',
249
- disabled = false
250
- }) => {
251
- const baseStyles: React.CSSProperties = {
252
- padding: '0.75rem 1.5rem',
253
- fontSize: '1rem',
254
- fontWeight: '600',
255
- borderRadius: '0.5rem',
256
- border: 'none',
257
- cursor: disabled ? 'not-allowed' : 'pointer',
258
- transition: 'all 0.2s ease',
259
- };
260
-
261
- const variantStyles: Record<string, React.CSSProperties> = {
262
- primary: {
263
- backgroundColor: disabled ? '#9ca3af' : '#3b82f6',
264
- color: '#ffffff',
265
- },
266
- secondary: {
267
- backgroundColor: disabled ? '#e5e7eb' : '#6b7280',
268
- color: '#ffffff',
269
- },
270
- };
271
-
272
- return (
273
- <button
274
- style={{ ...baseStyles, ...variantStyles[variant] }}
275
- onClick={onClick}
276
- disabled={disabled}
277
- >
278
- {label}
279
- </button>
280
- );
281
- };
282
-
283
- export default Button;
284
- ''';
285
- with open(components_dir / "Button.tsx", 'w') as f {
286
- f.write(button_tsx_content);
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;
287
311
  }
288
- print("Created src/components/Button.tsx");
289
-
290
- readme_content = f''' # {project_name}
291
-
292
-
293
- A Jac client-side application with React and TypeScript support.
294
-
295
- ## Project Structure
296
-
297
- ```
298
- {project_name}/
299
- ├── jac.toml # Project configuration
300
- ├── src/ # Source files
301
- │ ├── app.jac # Main application entry
302
- │ └── components/ # Reusable components
303
- │ └── Button.tsx # Example TypeScript component
304
- ├── assets/ # Static assets (images, fonts, etc.)
305
- └── build/ # Build output (generated)
306
- ```
307
-
308
- ## Getting Started
309
-
310
- Start the development server:
311
-
312
- ```bash
313
- jac serve src/app.jac
314
- ```
315
-
316
- ## TypeScript Support
317
312
 
318
- Create TypeScript components in `src/components/` and import them in your Jac files:
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);
319
316
 
320
- ```jac
321
- cl import from "./components/Button.tsx" {{ Button }}
322
- ```
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
+ }
323
323
 
324
- ## Adding Dependencies
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
+ }
325
333
 
326
- Add npm packages with the --cl flag:
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
+ }
327
344
 
328
- ```bash
329
- jac add --cl react-router-dom
330
- ```
331
- ''';
332
- with open(project_path / "README.md", 'w') as f {
333
- f.write(readme_content);
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);
351
+
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);
334
365
  }
335
- print("Created README.md");
366
+ }
336
367
 
337
- _create_gitignore(project_path, client=True);
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 }
338
372
 
339
- # Install default packages unless --skip is specified
340
- if not skip {
341
- print("\nInstalling default packages...");
342
- _install_default_packages(project_path, verbose);
343
- }
373
+ target_name = ctx.get_arg("client", "web");
344
374
 
345
- print(f"\nProject '{project_name}' created successfully!");
346
- if name and name != cwd.name {
347
- print("\nNext steps:");
348
- print(f" cd {name}");
349
- print(" jac serve src/app.jac");
350
- } else {
351
- print("\nNext steps:");
352
- print(" jac serve src/app.jac");
353
- }
354
- }
375
+ registry = get_target_registry();
376
+ target = registry.get(target_name);
355
377
 
356
- """Create .gitignore file with appropriate entries."""
357
- def _create_gitignore(project_path: pathlib.Path, client: bool = False) -> None {
358
- gitignore_entries = [
359
- "# Jac project",
360
- "packages/",
361
- ".jac_cache/",
362
- "*.jbc",
363
- "*.jir",
364
- "__jaccache__/",
365
- "",
366
- "# Python",
367
- "__pycache__/",
368
- "*.py[cod]",
369
- ".venv/",
370
- "venv/",
371
- "",
372
- "# IDE",
373
- ".idea/",
374
- ".vscode/",
375
- "*.swp"
376
- ];
377
-
378
- if client {
379
- gitignore_entries.extend(
380
- [
381
- "",
382
- "# Node.js",
383
- "node_modules/",
384
- "",
385
- "# Jac Client",
386
- ".client-build/",
387
- "*.session",
388
- "*.session.*"
389
- ]
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"
390
385
  );
386
+ ctx.set_data("cancel_execution", True);
387
+ ctx.set_data("cancel_return_code", 1);
388
+ return;
391
389
  }
392
390
 
393
- gitignore_path = project_path / ".gitignore";
394
- if gitignore_path.exists() {
395
- with open(gitignore_path, "r") as f {
396
- 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;
397
404
  }
398
- new_entries: list = [];
399
- for entry in gitignore_entries {
400
- if entry and entry not in existing_content {
401
- new_entries.append(entry);
402
- }
405
+ # Get entry file
406
+ entry_file = ctx.get_arg("filename", None);
407
+ if not entry_file {
408
+ entry_file = config.entry_point;
403
409
  }
404
- if new_entries {
405
- with open(gitignore_path, "a") as f {
406
- f.write("\n" + "\n".join(new_entries));
407
- }
408
- 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;
409
415
  }
410
- } else {
411
- with open(gitignore_path, "w") as f {
412
- f.write("\n".join(gitignore_entries) + "\n");
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;
413
420
  }
414
- print("Created .gitignore");
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;
428
+ }
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);
447
+ }
448
+ return;
415
449
  }
450
+ # Web target: let existing start command handle it
451
+ # (no cancellation, let it run normally)
416
452
  }
417
453
 
418
- """Install default npm packages in .client-build directory."""
419
- def _install_default_packages(
420
- project_path: pathlib.Path, verbose: bool = False
421
- ) -> None {
422
- 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
+ }
423
483
 
484
+ # Setup using target's setup method
424
485
  try {
425
- # Verify jac.toml exists
426
- toml_path = project_path / "jac.toml";
427
- if not toml_path.exists() {
428
- print(
429
- "Warning: jac.toml not found, skipping package installation",
430
- file=sys.stderr
431
- );
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);
432
491
  return;
433
492
  }
434
493
 
435
- # Get project name from jac.toml or use directory name
436
- project_name = project_path.name;
437
-
438
- # Create ViteBundler instance (it will load config internally)
439
- bundler = ViteBundler(project_path);
440
-
441
- # Generate package.json with default packages (defaults are added automatically)
442
- bundler.create_package_json(project_name=project_name);
443
-
444
- # Ensure .client-build directory exists
445
- build_dir = project_path / '.client-build';
446
- build_dir.mkdir(parents=True, exist_ok=True);
447
-
448
- # Copy package.json to .client-build/ for npm install
449
- configs_package_json = build_dir / '.jac-client.configs' / 'package.json';
450
- build_package_json = build_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);
451
499
 
452
- if not configs_package_json.exists() {
453
- print(
454
- "Warning: package.json was not generated, skipping package installation",
455
- 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}"
456
505
  );
506
+ console.print(
507
+ "This may indicate the setup function failed silently.", style="muted"
508
+ );
509
+ ctx.set_data("cancel_execution", True);
510
+ ctx.set_data("cancel_return_code", 1);
457
511
  return;
458
512
  }
459
513
 
460
- # Always copy the generated package.json to .client-build/ for npm install
461
- 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
+ }
462
529
 
463
- # Read package data for verbose output
464
- import json;
465
- with open(configs_package_json, 'r') as f {
466
- pkg_data = json.load(f);
467
- }
468
- deps = pkg_data.get('dependencies', {});
469
- dev_deps = pkg_data.get('devDependencies', {});
470
-
471
- if verbose {
472
- # Verbose mode: show detailed package list and stream npm output
473
- if deps {
474
- print(" Dependencies:");
475
- for (name, version) in deps.items() {
476
- print(f" - {name}@{version}");
477
- }
478
- }
479
- if dev_deps {
480
- print(" Dev dependencies:");
481
- for (name, version) in dev_deps.items() {
482
- print(f" - {name}@{version}");
483
- }
484
- }
485
- print("\nRunning npm install...");
486
- }
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
+ }
487
538
 
488
- # Run npm install in .client-build/ directory
489
- try {
490
- if verbose {
491
- # Stream output for visibility in verbose mode
492
- subprocess.run(
493
- ['npm', 'install', '--progress'], cwd=build_dir, check=True
494
- );
495
- } else {
496
- # Quiet mode: capture output
497
- subprocess.run(
498
- ['npm', 'install'],
499
- cwd=build_dir,
500
- check=True,
501
- capture_output=True,
502
- text=True
503
- );
504
- }
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 }
505
542
 
506
- # Move package-lock.json to .jac-client.configs/ if it was created
507
- build_package_lock = build_dir / 'package-lock.json';
508
- configs_dir = build_dir / '.jac-client.configs';
509
- configs_package_lock = configs_dir / 'package-lock.json';
510
- if build_package_lock.exists() {
511
- configs_dir.mkdir(parents=True, exist_ok=True);
512
- if configs_package_lock.exists() {
513
- configs_package_lock.unlink();
514
- }
515
- shutil.move(str(build_package_lock), str(configs_package_lock));
516
- }
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
+ }
517
550
 
518
- print("Default packages installed successfully");
519
- } except subprocess.CalledProcessError as e {
520
- if verbose {
521
- print(
522
- f"Warning: Failed to install packages (exit code {e.returncode})",
523
- file=sys.stderr
524
- );
525
- } else {
526
- print(
527
- f"Warning: Failed to install packages: {e.stderr}", file=sys.stderr
528
- );
529
- }
530
- print("You can install packages later with: jac add --cl", file=sys.stderr);
531
- } except FileNotFoundError {
532
- print(
533
- "Warning: npm command not found. Install Node.js and npm to install packages.",
534
- file=sys.stderr
535
- );
536
- print("You can install packages later with: jac add --cl", file=sys.stderr);
537
- } finally {
538
- # Clean up temporary package.json in .client-build/
539
- if build_package_json.exists() {
540
- build_package_json.unlink();
541
- }
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);
542
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);
543
579
  } except Exception as e {
544
- print(f"Warning: Could not install default packages: {e}", file=sys.stderr);
545
- 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);
546
583
  }
547
584
  }