jac-client 0.2.6__py3-none-any.whl → 0.2.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. jac_client/examples/all-in-one/app.jac +573 -0
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
  3. jac_client/examples/all-in-one/components/Header.jac +13 -0
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
  5. jac_client/examples/all-in-one/components/Summary.jac +53 -0
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
  8. jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
  9. jac_client/examples/all-in-one/components/navigation.jac +132 -0
  10. jac_client/examples/all-in-one/constants/categories.jac +37 -0
  11. jac_client/examples/all-in-one/constants/clients.jac +13 -0
  12. jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
  13. jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
  14. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
  15. jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +70 -0
  16. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +126 -0
  17. jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +552 -0
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +126 -0
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
  20. jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
  21. jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
  22. jac_client/examples/all-in-one/pages/notFound.jac +24 -0
  23. jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
  24. jac_client/examples/all-in-one/utils/formatters.jac +52 -0
  25. jac_client/examples/asset-serving/css-with-image/src/app.jac +3 -3
  26. jac_client/examples/asset-serving/image-asset/src/app.jac +3 -3
  27. jac_client/examples/asset-serving/import-alias/src/app.jac +3 -3
  28. jac_client/examples/basic/src/app.jac +3 -3
  29. jac_client/examples/basic-auth/src/app.jac +31 -37
  30. jac_client/examples/basic-auth-with-router/src/app.jac +16 -16
  31. jac_client/examples/basic-full-stack/src/app.jac +24 -30
  32. jac_client/examples/css-styling/js-styling/src/app.jac +5 -5
  33. jac_client/examples/css-styling/material-ui/src/app.jac +5 -5
  34. jac_client/examples/css-styling/pure-css/src/app.jac +5 -5
  35. jac_client/examples/css-styling/sass-example/src/app.jac +5 -5
  36. jac_client/examples/css-styling/styled-components/src/app.jac +5 -5
  37. jac_client/examples/css-styling/tailwind-example/src/app.jac +5 -5
  38. jac_client/examples/full-stack-with-auth/src/app.jac +16 -16
  39. jac_client/examples/ts-support/src/app.jac +4 -4
  40. jac_client/examples/with-router/src/app.jac +4 -4
  41. jac_client/plugin/cli.jac +160 -203
  42. jac_client/plugin/client.jac +8 -15
  43. jac_client/plugin/client_runtime.cl.jac +18 -14
  44. jac_client/plugin/impl/client.impl.jac +85 -26
  45. jac_client/plugin/impl/client_runtime.impl.jac +27 -9
  46. jac_client/plugin/plugin_config.jac +11 -11
  47. jac_client/plugin/src/compiler.jac +2 -1
  48. jac_client/plugin/src/impl/babel_processor.impl.jac +22 -17
  49. jac_client/plugin/src/impl/compiler.impl.jac +55 -18
  50. jac_client/plugin/src/impl/vite_bundler.impl.jac +215 -102
  51. jac_client/plugin/src/package_installer.jac +1 -1
  52. jac_client/plugin/src/vite_bundler.jac +9 -1
  53. jac_client/tests/conftest.py +10 -8
  54. jac_client/tests/fixtures/spawn_test/app.jac +15 -18
  55. jac_client/tests/fixtures/with-ts/app.jac +4 -4
  56. jac_client/tests/test_cli.py +105 -49
  57. jac_client/tests/test_it.py +297 -82
  58. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/METADATA +16 -7
  59. jac_client-0.2.8.dist-info/RECORD +97 -0
  60. jac_client/examples/all-in-one/src/app.jac +0 -841
  61. jac_client-0.2.6.dist-info/RECORD +0 -74
  62. /jac_client/examples/all-in-one/{src/button.jac → button.jac} +0 -0
  63. /jac_client/examples/all-in-one/{src/components → components}/button.jac +0 -0
  64. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/WHEEL +0 -0
  65. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/entry_points.txt +0 -0
  66. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/top_level.txt +0 -0
jac_client/plugin/cli.jac CHANGED
@@ -1,7 +1,7 @@
1
1
  """Command line interface tool for the Jac Client.
2
2
 
3
3
  This module extends the core `create` command to add client-side (frontend)
4
- project setup via the --cl flag.
4
+ project setup via the --cl flag using the extend_command API.
5
5
  """
6
6
 
7
7
  import os;
@@ -10,112 +10,82 @@ import sys;
10
10
  import pathlib;
11
11
  import subprocess;
12
12
  import shutil;
13
- import from jaclang.cli.cmdreg { cmd_registry, CommandPriority }
13
+ import from jaclang.cli.registry { get_registry }
14
+ import from jaclang.cli.command { Arg, ArgKind, HookContext }
14
15
  import from jaclang.pycore.runtime { hookimpl }
15
- import from jaclang.project.config { JacConfig, find_project_root }
16
+ import from jaclang.project.config { find_project_root }
16
17
 
17
18
  """Jac CLI extensions for client-side development."""
18
19
  class JacCmd {
19
20
  """Create Jac CLI cmds."""
20
21
  @hookimpl
21
22
  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
- }
59
-
60
- # Register with PLUGIN priority to override core's create command
61
- cmd_registry.register(
62
- create, priority=CommandPriority.PLUGIN, source="jac-client"
23
+ """Extend core create command to add --cl flag for client-side setup.""";
24
+ registry = get_registry();
25
+
26
+ # Extend the core 'create' command with client-specific arguments
27
+ registry.extend_command(
28
+ command_name="create",
29
+ args=[
30
+ Arg.create(
31
+ "cl",
32
+ typ=bool,
33
+ default=False,
34
+ help="Include client-side (frontend) setup",
35
+ short="c"
36
+ ),
37
+ Arg.create(
38
+ "skip",
39
+ typ=bool,
40
+ default=False,
41
+ help="Skip installing default packages (only for --cl)",
42
+ short="s"
43
+ ),
44
+ Arg.create(
45
+ "verbose",
46
+ typ=bool,
47
+ default=False,
48
+ help="Show detailed output during installation",
49
+ short="v"
50
+ ),
51
+
52
+ ],
53
+ pre_hook=_handle_client_create,
54
+ source="jac-client"
63
55
  );
64
56
  }
65
57
  }
66
58
 
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
- }
76
-
77
- project_name = name or cwd.name;
78
-
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);
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
65
+ return;
83
66
  }
84
67
 
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
- '''
101
- );
102
- }
103
- print(f"Created {main_jac}");
104
- }
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);
105
74
 
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}/");
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);
86
+ ctx.set_data("cancel_execution", True);
87
+ ctx.set_data("cancel_return_code", 1);
110
88
  }
111
-
112
- _create_gitignore(cwd, client=False);
113
-
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
89
  }
120
90
 
121
91
  """Create a client-side Jac project with organized folder structure."""
@@ -166,23 +136,34 @@ def _create_client_project(
166
136
 
167
137
  print(f"Creating Jac client application: {project_name}");
168
138
 
169
- src_dir = project_path / "src";
170
- src_dir.mkdir(parents=True, exist_ok=True);
171
-
172
- components_dir = src_dir / "components";
139
+ components_dir = project_path / "components";
173
140
  components_dir.mkdir(parents=True, exist_ok=True);
174
141
 
175
142
  assets_dir = project_path / "assets";
176
143
  assets_dir.mkdir(parents=True, exist_ok=True);
177
144
 
178
- (project_path / ".client-build").mkdir(parents=True, exist_ok=True);
145
+ (project_path / ".jac" / "client").mkdir(parents=True, exist_ok=True);
179
146
 
180
147
  toml_path = project_path / "jac.toml";
181
148
  toml_content = f'''[project]
182
149
  name = "{project_name}"
183
150
  version = "1.0.0"
184
151
  description = "Jac client application: {project_name}"
185
- entry-point = "src/app.jac"
152
+ entry-point = "main.jac"
153
+
154
+ [dependencies]
155
+
156
+ [dependencies.npm]
157
+ "jac-client-node" = "1.0.3"
158
+
159
+ [dependencies.npm.dev]
160
+ "@jac-client/dev-deps" = "1.0.0"
161
+
162
+ [dev-dependencies]
163
+ watchdog = ">=3.0.0"
164
+
165
+ [serve]
166
+ base_route_app = "app"
186
167
 
187
168
  [plugins.client]
188
169
  # Vite bundler configuration (optional overrides)
@@ -194,16 +175,16 @@ entry-point = "src/app.jac"
194
175
  }
195
176
  print(f"Created {toml_path}");
196
177
 
197
- app_jac_content = '''"""Main entry point for the Jac client application."""
178
+ main_jac_content = '''"""Main entry point for the Jac client application."""
198
179
 
199
- # Client-side imports
200
- cl import from react { useState, useEffect }
201
- cl import from ".components/Button.tsx" { Button }
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 }
202
183
 
203
184
  # Client-side component
204
185
  cl {
205
186
  def:pub app() -> any {
206
- [count, setCount] = useState(0);
187
+ has count: int = 0;
207
188
 
208
189
  useEffect(lambda -> None {
209
190
  console.log("Count updated:", count);
@@ -215,12 +196,12 @@ cl {
215
196
  <div style={{display: "flex", gap: "1rem", marginTop: "1rem"}}>
216
197
  <Button
217
198
  label="Increment"
218
- onClick={lambda -> None { setCount(count + 1); }}
199
+ onClick={lambda -> None { count = count + 1; }}
219
200
  variant="primary"
220
201
  />
221
202
  <Button
222
203
  label="Reset"
223
- onClick={lambda -> None { setCount(0); }}
204
+ onClick={lambda -> None { count = 0; }}
224
205
  variant="secondary"
225
206
  />
226
207
  </div>
@@ -228,79 +209,62 @@ cl {
228
209
  }
229
210
  }
230
211
  ''';
231
- with open(src_dir / "app.jac", 'w') as f {
232
- f.write(app_jac_content);
212
+ with open(project_path / "main.jac", 'w') as f {
213
+ f.write(main_jac_content);
233
214
  }
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
- }
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
+ };
244
240
 
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}
241
+ return <button
242
+ style={{**base_styles, **variant_styles[variant]}}
243
+ onClick={onClick}
244
+ disabled={disabled}
277
245
  >
278
- {label}
279
- </button>
280
- );
281
- };
282
-
283
- export default Button;
246
+ {label}
247
+ </button>;
248
+ }
284
249
  ''';
285
- with open(components_dir / "Button.tsx", 'w') as f {
286
- f.write(button_tsx_content);
250
+ with open(components_dir / "Button.cl.jac", 'w') as f {
251
+ f.write(button_cl_jac_content);
287
252
  }
288
- print("Created src/components/Button.tsx");
253
+ print("Created components/Button.cl.jac");
289
254
 
290
255
  readme_content = f''' # {project_name}
291
256
 
292
257
 
293
- A Jac client-side application with React and TypeScript support.
258
+ A Jac client-side application with React support.
294
259
 
295
260
  ## Project Structure
296
261
 
297
262
  ```
298
263
  {project_name}/
299
264
  ├── jac.toml # Project configuration
300
- ├── src/ # Source files
301
- ├── app.jac # Main application entry
302
- │ └── components/ # Reusable components
303
- │ └── Button.tsx # Example TypeScript component
265
+ ├── main.jac # Main application entry
266
+ ├── components/ # Reusable components
267
+ │ └── Button.cl.jac # Example Jac component
304
268
  ├── assets/ # Static assets (images, fonts, etc.)
305
269
  └── build/ # Build output (generated)
306
270
  ```
@@ -310,15 +274,15 @@ A Jac client-side application with React and TypeScript support.
310
274
  Start the development server:
311
275
 
312
276
  ```bash
313
- jac serve src/app.jac
277
+ jac start main.jac
314
278
  ```
315
279
 
316
- ## TypeScript Support
280
+ ## Components
317
281
 
318
- Create TypeScript components in `src/components/` and import them in your Jac files:
282
+ Create Jac components in `components/` as `.cl.jac` files and import them:
319
283
 
320
284
  ```jac
321
- cl import from "./components/Button.tsx" {{ Button }}
285
+ cl import from .components.Button {{ Button }}
322
286
  ```
323
287
 
324
288
  ## Adding Dependencies
@@ -334,7 +298,7 @@ jac add --cl react-router-dom
334
298
  }
335
299
  print("Created README.md");
336
300
 
337
- _create_gitignore(project_path, client=True);
301
+ _create_gitignore(project_path);
338
302
 
339
303
  # Install default packages unless --skip is specified
340
304
  if not skip {
@@ -346,15 +310,15 @@ jac add --cl react-router-dom
346
310
  if name and name != cwd.name {
347
311
  print("\nNext steps:");
348
312
  print(f" cd {name}");
349
- print(" jac serve src/app.jac");
313
+ print(" jac start main.jac");
350
314
  } else {
351
315
  print("\nNext steps:");
352
- print(" jac serve src/app.jac");
316
+ print(" jac start main.jac");
353
317
  }
354
318
  }
355
319
 
356
- """Create .gitignore file with appropriate entries."""
357
- def _create_gitignore(project_path: pathlib.Path, client: bool = False) -> None {
320
+ """Create .gitignore file with client-specific entries."""
321
+ def _create_gitignore(project_path: pathlib.Path) -> None {
358
322
  gitignore_entries = [
359
323
  "# Jac project",
360
324
  "packages/",
@@ -372,24 +336,17 @@ def _create_gitignore(project_path: pathlib.Path, client: bool = False) -> None
372
336
  "# IDE",
373
337
  ".idea/",
374
338
  ".vscode/",
375
- "*.swp"
339
+ "*.swp",
340
+ "",
341
+ "# Node.js",
342
+ "node_modules/",
343
+ "",
344
+ "# Jac build artifacts",
345
+ ".jac/",
346
+ "*.session",
347
+ "*.session.*"
376
348
  ];
377
349
 
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
- ]
390
- );
391
- }
392
-
393
350
  gitignore_path = project_path / ".gitignore";
394
351
  if gitignore_path.exists() {
395
352
  with open(gitignore_path, "r") as f {
@@ -415,7 +372,7 @@ def _create_gitignore(project_path: pathlib.Path, client: bool = False) -> None
415
372
  }
416
373
  }
417
374
 
418
- """Install default npm packages in .client-build directory."""
375
+ """Install default npm packages in .jac/client directory."""
419
376
  def _install_default_packages(
420
377
  project_path: pathlib.Path, verbose: bool = False
421
378
  ) -> None {
@@ -441,13 +398,13 @@ def _install_default_packages(
441
398
  # Generate package.json with default packages (defaults are added automatically)
442
399
  bundler.create_package_json(project_name=project_name);
443
400
 
444
- # Ensure .client-build directory exists
445
- build_dir = project_path / '.client-build';
446
- build_dir.mkdir(parents=True, exist_ok=True);
401
+ # Ensure .jac/client directory exists
402
+ client_dir = bundler._get_client_dir();
403
+ client_dir.mkdir(parents=True, exist_ok=True);
447
404
 
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';
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';
451
408
 
452
409
  if not configs_package_json.exists() {
453
410
  print(
@@ -457,7 +414,7 @@ def _install_default_packages(
457
414
  return;
458
415
  }
459
416
 
460
- # Always copy the generated package.json to .client-build/ for npm install
417
+ # Always copy the generated package.json to .jac/client/ for npm install
461
418
  shutil.copy2(configs_package_json, build_package_json);
462
419
 
463
420
  # Read package data for verbose output
@@ -485,27 +442,27 @@ def _install_default_packages(
485
442
  print("\nRunning npm install...");
486
443
  }
487
444
 
488
- # Run npm install in .client-build/ directory
445
+ # Run npm install in .jac/client/ directory
489
446
  try {
490
447
  if verbose {
491
448
  # Stream output for visibility in verbose mode
492
449
  subprocess.run(
493
- ['npm', 'install', '--progress'], cwd=build_dir, check=True
450
+ ['npm', 'install', '--progress'], cwd=client_dir, check=True
494
451
  );
495
452
  } else {
496
453
  # Quiet mode: capture output
497
454
  subprocess.run(
498
455
  ['npm', 'install'],
499
- cwd=build_dir,
456
+ cwd=client_dir,
500
457
  check=True,
501
458
  capture_output=True,
502
459
  text=True
503
460
  );
504
461
  }
505
462
 
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';
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';
509
466
  configs_package_lock = configs_dir / 'package-lock.json';
510
467
  if build_package_lock.exists() {
511
468
  configs_dir.mkdir(parents=True, exist_ok=True);
@@ -535,7 +492,7 @@ def _install_default_packages(
535
492
  );
536
493
  print("You can install packages later with: jac add --cl", file=sys.stderr);
537
494
  } finally {
538
- # Clean up temporary package.json in .client-build/
495
+ # Clean up temporary package.json in .jac/client/
539
496
  if build_package_json.exists() {
540
497
  build_package_json.unlink();
541
498
  }
@@ -18,16 +18,6 @@ glob JsonValue: TypeAlias = None | str | int | float | bool | list['JsonValue']
18
18
  ],
19
19
  StatusCode: TypeAlias = Literal[(200, 201, 400, 401, 404, 503)];
20
20
 
21
- """Jac Client Module Introspector."""
22
- class JacClientModuleIntrospector(ModuleIntrospector) {
23
- def render_page(
24
- self: JacClientModuleIntrospector,
25
- function_name: str,
26
- args: dict[(str, Any)],
27
- username: str
28
- ) -> dict[str, Any];
29
- }
30
-
31
21
  """Jac Client."""
32
22
  class JacClient {
33
23
  @hookimpl
@@ -38,15 +28,18 @@ class JacClient {
38
28
  module: types.ModuleType, force: bool = False
39
29
  ) -> ClientBundle;
40
30
 
41
- @hookimpl
42
- static def get_module_introspector(
43
- module_name: str, base_path: (str | None)
44
- ) -> ModuleIntrospector;
45
-
46
31
  @hookimpl
47
32
  static def send_static_file(
48
33
  handler: BaseHTTPRequestHandler,
49
34
  file_path: Path,
50
35
  content_type: (str | None) = None
51
36
  ) -> None;
37
+
38
+ @hookimpl
39
+ static def render_page(
40
+ introspector: ModuleIntrospector,
41
+ function_name: str,
42
+ args: dict[(str, Any)],
43
+ username: str
44
+ ) -> dict[str, Any];
52
45
  }
@@ -1,6 +1,7 @@
1
1
  """Client-side runtime for Jac JSX and walker interactions."""
2
2
 
3
3
  import from 'react' { * as React }
4
+ import from 'react' { useState as reactUseState }
4
5
  import from 'react-dom/client' { * as ReactDOM }
5
6
  import from 'react-router-dom' {
6
7
  HashRouter as ReactRouterHashRouter,
@@ -13,9 +14,11 @@ import from 'react-router-dom' {
13
14
  useParams as reactRouterUseParams
14
15
  }
15
16
 
16
- def : pub __jacJsx(tag: any, props: dict = {}, children: any = []) -> any;
17
+ def:pub __jacJsx(tag: any, props: dict = {}, children: any = []) -> any;
17
18
 
18
- glob : pub Router = ReactRouterHashRouter,
19
+ # React hooks re-exported for auto-injection by `has` variables
20
+ glob:pub useState = reactUseState,
21
+ Router = ReactRouterHashRouter,
19
22
  Routes = ReactRouterRoutes,
20
23
  Route = ReactRouterRoute,
21
24
  Link = ReactRouterLink,
@@ -24,15 +27,16 @@ glob : pub Router = ReactRouterHashRouter,
24
27
  useLocation = reactRouterUseLocation,
25
28
  useParams = reactRouterUseParams;
26
29
 
27
- def : pub useRouter -> dict;
28
- def : pub navigate(path: str) -> None;
29
- async def : pub __jacSpawn(left: str, right: str = "", fields: dict = {}) -> any;
30
- def : pub jacSpawn(left: str, right: str = "", fields: dict = {}) -> any;
31
- async def : pub __jacCallFunction(function_name: str, args: dict = {}) -> any;
32
- async def : pub jacSignup(username: str, password: str) -> dict;
33
- async def : pub jacLogin(username: str, password: str) -> bool;
34
- def : pub jacLogout -> None;
35
- def : pub jacIsLoggedIn -> bool;
36
- def : pub __getLocalStorage(key: str) -> str;
37
- def : pub __setLocalStorage(key: str, value: str) -> None;
38
- def : pub __removeLocalStorage(key: str) -> None;
30
+ def:pub useRouter -> dict;
31
+ def:pub navigate(path: str) -> None;
32
+ async def:pub __jacSpawn(left: str, right: str = "", fields: dict = {}) -> any;
33
+ def:pub jacSpawn(left: str, right: str = "", fields: dict = {}) -> any;
34
+ async def:pub __jacCallFunction(function_name: str, args: dict = {}) -> any;
35
+ async def:pub jacSignup(username: str, password: str) -> dict;
36
+ async def:pub jacLogin(username: str, password: str) -> bool;
37
+ def:pub jacLogout -> None;
38
+ def:pub jacIsLoggedIn -> bool;
39
+ def:pub __getLocalStorage(key: str) -> str;
40
+ def:pub __setLocalStorage(key: str, value: str) -> None;
41
+ def:pub __removeLocalStorage(key: str) -> None;
42
+ # React Router components