jac-client 0.2.10__py3-none-any.whl → 0.2.12__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 (85) hide show
  1. jac_client/examples/all-in-one/button.jac +4 -3
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +36 -24
  3. jac_client/examples/all-in-one/components/Header.jac +12 -8
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +49 -35
  5. jac_client/examples/all-in-one/components/Summary.jac +59 -36
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +142 -112
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +37 -30
  8. jac_client/examples/all-in-one/components/TransactionList.jac +33 -26
  9. jac_client/examples/all-in-one/components/button.jac +4 -3
  10. jac_client/examples/all-in-one/components/navigation.jac +111 -117
  11. jac_client/examples/all-in-one/constants/categories.jac +23 -24
  12. jac_client/examples/all-in-one/constants/clients.jac +7 -8
  13. jac_client/examples/all-in-one/context/BudgetContext.jac +9 -6
  14. jac_client/examples/all-in-one/hooks/useBudget.jac +18 -12
  15. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +14 -13
  16. jac_client/examples/all-in-one/main.jac +340 -371
  17. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +19 -12
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +31 -15
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +113 -90
  20. jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +34 -39
  21. jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +464 -352
  22. jac_client/examples/all-in-one/pages/loginPage.jac +114 -119
  23. jac_client/examples/all-in-one/pages/nestedDemo.jac +43 -50
  24. jac_client/examples/all-in-one/pages/notFound.jac +14 -15
  25. jac_client/examples/all-in-one/pages/signupPage.jac +113 -119
  26. jac_client/examples/all-in-one/utils/formatters.jac +5 -8
  27. jac_client/examples/asset-serving/css-with-image/main.jac +77 -73
  28. jac_client/examples/asset-serving/image-asset/main.jac +47 -46
  29. jac_client/examples/asset-serving/import-alias/main.jac +93 -95
  30. jac_client/examples/basic/main.jac +17 -15
  31. jac_client/examples/basic-auth/main.jac +246 -254
  32. jac_client/examples/basic-auth-with-router/main.jac +272 -285
  33. jac_client/examples/basic-full-stack/main.jac +245 -242
  34. jac_client/examples/css-styling/js-styling/main.jac +41 -62
  35. jac_client/examples/css-styling/material-ui/main.jac +90 -90
  36. jac_client/examples/css-styling/pure-css/main.jac +35 -44
  37. jac_client/examples/css-styling/sass-example/main.jac +35 -44
  38. jac_client/examples/css-styling/styled-components/main.jac +38 -47
  39. jac_client/examples/css-styling/tailwind-example/main.jac +54 -43
  40. jac_client/examples/full-stack-with-auth/main.jac +407 -433
  41. jac_client/examples/little-x/main.jac +306 -344
  42. jac_client/examples/little-x/src/submit-button.jac +15 -14
  43. jac_client/examples/nested-folders/nested-advance/main.jac +18 -27
  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/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 +26 -26
  52. jac_client/examples/with-router/main.jac +186 -223
  53. jac_client/plugin/client_runtime.cl.jac +5 -3
  54. jac_client/plugin/impl/client_runtime.impl.jac +1 -1
  55. jac_client/plugin/plugin_config.jac +53 -99
  56. jac_client/plugin/src/__init__.jac +0 -2
  57. jac_client/plugin/src/compiler.jac +0 -1
  58. jac_client/plugin/src/impl/compiler.impl.jac +49 -17
  59. jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
  60. jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
  61. jac_client/plugin/src/impl/vite_bundler.impl.jac +146 -84
  62. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +54 -41
  63. jac_client/plugin/utils/__init__.jac +3 -0
  64. jac_client/plugin/utils/bun_installer.jac +16 -0
  65. jac_client/plugin/utils/client_deps.jac +14 -0
  66. jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
  67. jac_client/plugin/utils/impl/client_deps.impl.jac +73 -0
  68. jac_client/templates/client.jacpack +0 -4
  69. jac_client/templates/fullstack.jacpack +1 -5
  70. jac_client/tests/conftest.py +56 -41
  71. jac_client/tests/fixtures/spawn_test/app.jac +49 -52
  72. jac_client/tests/fixtures/with-ts/app.jac +27 -27
  73. jac_client/tests/test_cli.py +71 -6
  74. jac_client/tests/test_helpers.py +11 -18
  75. jac_client/tests/test_it.py +1 -1
  76. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/METADATA +5 -5
  77. jac_client-0.2.12.dist-info/RECORD +115 -0
  78. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/WHEEL +1 -1
  79. jac_client/plugin/src/babel_processor.jac +0 -18
  80. jac_client/plugin/src/impl/babel_processor.impl.jac +0 -89
  81. jac_client/plugin/utils/impl/node_installer.impl.jac +0 -249
  82. jac_client/plugin/utils/node_installer.jac +0 -41
  83. jac_client-0.2.10.dist-info/RECORD +0 -115
  84. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/entry_points.txt +0 -0
  85. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/top_level.txt +0 -0
@@ -44,17 +44,14 @@ impl ViteBundler.create_package_json(
44
44
  }
45
45
  dependencies = package_config.get('dependencies', {});
46
46
  dev_dependencies = package_config.get('devDependencies', {});
47
+ # Vite handles JSX/TSX transpilation natively with Bun - no Babel compile step needed
47
48
  scripts = {
48
- 'build': 'npm run compile && vite build --config .jac/client/configs/vite.config.js',
49
+ 'build': 'vite build --config .jac/client/configs/vite.config.js',
49
50
  'dev': 'vite dev --config .jac/client/configs/vite.config.js',
50
- 'preview': 'vite preview --config .jac/client/configs/vite.config.js',
51
- 'compile': 'babel compiled --out-dir build --extensions ".jsx,.js" --out-file-extension .js'
51
+ 'preview': 'vite preview --config .jac/client/configs/vite.config.js'
52
52
  };
53
53
  user_scripts = package_config.get('scripts', {});
54
54
  scripts.update(user_scripts);
55
- babel_config = {
56
- 'presets': [['@babel/preset-env', {'modules': False}], '@babel/preset-react']
57
- };
58
55
  package_data = {
59
56
  'name': name,
60
57
  'version': package_config.get('version', '1.0.0'),
@@ -63,8 +60,7 @@ impl ViteBundler.create_package_json(
63
60
  'main': 'index.js',
64
61
  'scripts': scripts,
65
62
  'dependencies': dependencies,
66
- 'devDependencies': dev_dependencies,
67
- 'babel': babel_config
63
+ 'devDependencies': dev_dependencies
68
64
  };
69
65
  for (key, value) in package_config.items() {
70
66
  if (
@@ -131,22 +127,22 @@ impl ViteBundler.create_tsconfig(self: ViteBundler) -> Path {
131
127
  }
132
128
 
133
129
  """
134
- Clean up root package.json and move package-lock.json to configs/.
130
+ Clean up root package.json and move bun.lockb to configs/.
135
131
 
136
- Remove root package.json and move package-lock.json to configs/.
132
+ Remove root package.json and move bun.lockb to configs/.
137
133
  """
138
134
  impl ViteBundler._cleanup_root_package_files(self: ViteBundler) -> None {
139
135
  root_package_json = self.project_dir / 'package.json';
140
- root_package_lock = self.project_dir / 'package-lock.json';
136
+ root_bun_lockb = self.project_dir / 'bun.lockb';
141
137
  build_dir = self._get_client_dir();
142
138
  configs_dir = build_dir / 'configs';
143
- configs_package_lock = configs_dir / 'package-lock.json';
144
- if root_package_lock.exists() {
139
+ configs_bun_lockb = configs_dir / 'bun.lockb';
140
+ if root_bun_lockb.exists() {
145
141
  configs_dir.mkdir(exist_ok=True);
146
- if configs_package_lock.exists() {
147
- configs_package_lock.unlink();
142
+ if configs_bun_lockb.exists() {
143
+ configs_bun_lockb.unlink();
148
144
  }
149
- shutil.move(str(root_package_lock), str(configs_package_lock));
145
+ shutil.move(str(root_bun_lockb), str(configs_bun_lockb));
150
146
  }
151
147
  if root_package_json.exists() {
152
148
  root_package_json.unlink();
@@ -154,7 +150,7 @@ impl ViteBundler._cleanup_root_package_files(self: ViteBundler) -> None {
154
150
  }
155
151
 
156
152
  """
157
- Ensure root package.json exists temporarily for npm commands.
153
+ Ensure root package.json exists temporarily for bun commands.
158
154
 
159
155
  Create root package.json temporarily if it doesn't exist.
160
156
  """
@@ -303,26 +299,76 @@ impl ViteBundler.create_vite_config(self: ViteBundler, entry_file: Path) -> Path
303
299
  else '';
304
300
  config_content = f'''import {{ defineConfig }} from "vite";
305
301
  import path from "path";
302
+ import fs from "fs";
306
303
  import {{ fileURLToPath }} from "url";
307
304
  {imports_section}const __dirname = path.dirname(fileURLToPath(import.meta.url));
308
305
  // Config is in configs/ inside .jac/client/, so go up one level to .jac/client/, then up two more to project root
309
306
  const buildDir = path.resolve(__dirname, "..");
310
307
  const projectRoot = path.resolve(__dirname, "../../..");
311
308
 
309
+ // Jac source mapper plugin - maps errors back to original .jac files
310
+ function jacSourceMapper() {{
311
+ const sourceMap = new Map(); // compiled path -> original jac path
312
+
313
+ return {{
314
+ name: 'jac-source-mapper',
315
+ enforce: 'pre',
316
+
317
+ // Extract source mapping from compiled files
318
+ transform(code, id) {{
319
+ if (id.includes('/compiled/') && id.endsWith('.js')) {{
320
+ const match = code.match(/^\\/\\* Source: (.+?) \\*\\//);
321
+ if (match) {{
322
+ sourceMap.set(id, match[1]);
323
+ }}
324
+ }}
325
+ return null;
326
+ }},
327
+
328
+ // Enhance error messages with original source info
329
+ buildEnd() {{
330
+ // Store source map for error reporting
331
+ this._jacSourceMap = sourceMap;
332
+ }},
333
+
334
+ // Handle resolve errors to show original source
335
+ resolveId(source, importer) {{
336
+ if (importer && sourceMap.has(importer)) {{
337
+ const originalSource = sourceMap.get(importer);
338
+ // Check for common issues like double slashes
339
+ if (source.includes('//') && !source.startsWith('http')) {{
340
+ this.error({{
341
+ message: `Cannot resolve "${{source}}" - path contains invalid double slash. Check your import in the original Jac file.`,
342
+ id: originalSource,
343
+ loc: {{ line: 1, column: 0 }}
344
+ }});
345
+ }}
346
+ }}
347
+ return null;
348
+ }}
349
+ }};
350
+ }}
351
+
312
352
  /**
313
353
  * Vite configuration generated from config.json (in project root)
314
354
  * To customize, edit config.json instead of this file.
315
355
  */
316
356
 
317
357
  export default defineConfig({{
318
- plugins: [{(newline + plugins_str + newline + ' ') if plugins_str else ''}],
358
+ plugins: [
359
+ jacSourceMapper(),{(newline + plugins_str + newline + ' ') if plugins_str else ''}],
319
360
  root: buildDir, // base folder (.jac/client/) so vite can find node_modules
320
361
  build: {{
362
+ sourcemap: true, // Enable source maps for better error messages
321
363
  rollupOptions: {{
322
364
  input: path.resolve(buildDir, "{entry_relative}"), // your compiled entry file
323
365
  output: {{
324
366
  entryFileNames: "client.[hash].js", // name of the final js file
325
367
  assetFileNames: (assetInfo) => assetInfo.name?.endsWith('.css') ? 'styles.css' : '[name].[ext]',
368
+ sourcemapPathTransform: (relativeSourcePath) => {{
369
+ // Transform source map paths to point to original location
370
+ return relativeSourcePath;
371
+ }},
326
372
  }},
327
373
  }},
328
374
  outDir: path.resolve(buildDir, "{output_relative}"), // final bundled output
@@ -332,7 +378,7 @@ export default defineConfig({{
332
378
  publicDir: false,
333
379
  {server_section} resolve: {{
334
380
  alias: {{
335
- "@jac-client/utils": path.resolve(buildDir, "{compiled_utils_relative}"),
381
+ "@jac/runtime": path.resolve(buildDir, "{compiled_utils_relative}"),
336
382
  "@jac-client/assets": path.resolve(buildDir, "{compiled_assets_relative}"),
337
383
  }},
338
384
  extensions: [{extensions_str}],
@@ -379,19 +425,32 @@ impl ViteBundler.find_bundle(self: ViteBundler) -> Optional[Path] {
379
425
  impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) -> None {
380
426
  import sys;
381
427
  import time;
382
- self.output_dir.mkdir(parents=True, exist_ok=True);
383
- generated_package_json = self._get_client_dir() / 'configs' / 'package.json';
384
- if not generated_package_json.exists() {
385
- self.create_package_json();
386
- } else {
387
- # Ensure tsconfig.json exists even if package.json already exists
388
- self.create_tsconfig();
428
+ import shutil;
429
+ import from jac_client.plugin.utils { ensure_bun_available, ensure_client_deps }
430
+ # Ensure bun is available before proceeding
431
+ if not ensure_bun_available() {
432
+ raise ClientBundleError('Bun is required. Install manually: https://bun.sh') from None ;
433
+ }
434
+ # Ensure client npm deps are configured; prompt to install defaults if missing
435
+ if not ensure_client_deps(self.config_loader) {
436
+ raise ClientBundleError(
437
+ 'Client dependencies not configured. Add [dependencies.npm] to jac.toml '
438
+ 'or create a project with: jac create --use client'
439
+ ) from None ;
389
440
  }
441
+ self.output_dir.mkdir(parents=True, exist_ok=True);
442
+ # Always regenerate package.json to pick up any dependency changes
443
+ self.create_package_json();
390
444
  try {
391
445
  build_dir = self._get_client_dir();
392
446
  node_modules = build_dir / 'node_modules';
447
+ # Reinstall if node_modules is missing or stale (e.g. empty from a prior failed install)
448
+ vite_bin = node_modules / '.bin' / 'vite';
449
+ if node_modules.exists() and not vite_bin.exists() {
450
+ shutil.rmtree(node_modules);
451
+ }
393
452
  if not node_modules.exists() {
394
- # Temporarily copy package.json to client build dir for npm install
453
+ # Temporarily copy package.json to client build dir for bun install
395
454
  build_package_json = build_dir / 'package.json';
396
455
  configs_package_json = build_dir / 'configs' / 'package.json';
397
456
  if configs_package_json.exists() and not build_package_json.exists() {
@@ -400,82 +459,82 @@ impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) ->
400
459
  }
401
460
  try {
402
461
  # Install to .jac/client/node_modules with progress feedback
403
- print(
404
- " ⏳ Installing npm dependencies (this may take a minute)...",
405
- flush=True
406
- );
462
+ print("\n ⏳ Installing dependencies...\n", flush=True);
407
463
  start_time = time.time();
408
464
  result = subprocess.run(
409
- ['npm', 'install', '--progress'],
465
+ ['bun', 'install'],
410
466
  cwd=build_dir,
411
467
  check=False,
412
- capture_output=True,
413
- text=True
468
+ text=True,
469
+ capture_output=True
414
470
  );
415
471
  elapsed = time.time() - start_time;
416
472
  if result.returncode != 0 {
417
- print(
418
- f"\n ✖ npm install failed after {elapsed:.1f}s",
419
- file=sys.stderr
420
- );
421
- raise ClientBundleError(
422
- f"Failed to install npm dependencies: {result.stderr
423
- or result.stdout}"
424
- ) ;
473
+ error_output = result.stderr or result.stdout;
474
+ error_msg = f"Dependency installation failed after {elapsed:.1f}s\n\n{error_output}\nCommand: bun install";
475
+ raise ClientBundleError(error_msg) from None ;
425
476
  }
426
- print(f" ✔ Dependencies installed ({elapsed:.1f}s)", flush=True);
477
+ print(f"\n ✔ Dependencies installed ({elapsed:.1f}s)", flush=True);
427
478
  } except FileNotFoundError {
428
- raise None from ClientBundleError(
429
- 'npm command not found. Ensure Node.js and npm are installed.'
430
- ) ;
479
+ # This shouldn't happen since we check for bun at the start
480
+ raise ClientBundleError(
481
+ 'Bun command not found. Install Bun: https://bun.sh'
482
+ ) from None ;
431
483
  }
432
484
  }
433
485
  if self.config_path {
434
486
  # Make config path relative to build_dir (where vite runs from)
435
487
  try {
436
488
  config_rel = self.config_path.relative_to(build_dir);
437
- command = ['npx', 'vite', 'build', '--config', str(config_rel)];
489
+ command = ['bun', 'x', 'vite', 'build', '--config', str(config_rel)];
438
490
  } except ValueError {
439
491
  # Config is outside client build dir, use absolute path
440
- command = ['npx', 'vite', 'build', '--config', str(self.config_path)];
492
+ command = [
493
+ 'bun',
494
+ 'x',
495
+ 'vite',
496
+ 'build',
497
+ '--config',
498
+ str(self.config_path)
499
+ ];
441
500
  }
442
501
  } elif entry_file {
443
502
  generated_config = self.create_vite_config(entry_file);
444
503
  # Config is in configs/, make it relative to build_dir
445
504
  config_rel = generated_config.relative_to(build_dir);
446
- command = ['npx', 'vite', 'build', '--config', str(config_rel)];
505
+ command = ['bun', 'x', 'vite', 'build', '--config', str(config_rel)];
447
506
  } else {
448
- command = ['npm', 'run', 'build'];
507
+ command = ['bun', 'run', 'build'];
449
508
  }
450
509
  # Run vite from client build directory so it can find node_modules
451
- print(" ⏳ Building client bundle...", flush=True);
510
+ print("\n ⏳ Building client bundle...\n", flush=True);
452
511
  start_time = time.time();
453
512
  result = subprocess.run(
454
- command, cwd=build_dir, check=False, capture_output=True, text=True
513
+ command, cwd=build_dir, check=False, text=True, capture_output=True
455
514
  );
456
515
  elapsed = time.time() - start_time;
457
516
  if result.returncode != 0 {
458
- print(f"\n ✖ Vite build failed after {elapsed:.1f}s", file=sys.stderr);
459
- error_msg = result.stderr or result.stdout or 'Unknown error';
460
- raise ClientBundleError(
461
- f"Vite build failed:\n{error_msg}\nCommand: {' '.join(command)}"
462
- ) from None ;
517
+ error_output = result.stderr or result.stdout;
518
+ error_msg = f"Vite build failed after {elapsed:.1f}s\n\n{error_output}\nCommand: {' '.join(
519
+ command
520
+ )}";
521
+ raise ClientBundleError(error_msg) from None ;
463
522
  }
464
- print(f" ✔ Client bundle built ({elapsed:.1f}s)", flush=True);
523
+ print(f"\n ✔ Client bundle built ({elapsed:.1f}s)", flush=True);
465
524
  } finally {
466
525
  # Clean up temporary package.json in client build dir
467
526
  build_package_json = build_dir / 'package.json';
468
527
  if build_package_json.exists() {
469
528
  build_package_json.unlink();
470
529
  }
471
- # Move package-lock.json to configs/ if it exists
472
- build_package_lock = build_dir / 'package-lock.json';
473
- if build_package_lock.exists() {
474
- configs_package_lock = build_dir / 'configs' / 'package-lock.json';
475
- if configs_package_lock.exists() {
476
- configs_package_lock.unlink();
530
+ # Move bun.lockb to configs/ if it exists
531
+ build_bun_lockb = build_dir / 'bun.lockb';
532
+ if build_bun_lockb.exists() {
533
+ configs_bun_lockb = build_dir / 'configs' / 'bun.lockb';
534
+ if configs_bun_lockb.exists() {
535
+ configs_bun_lockb.unlink();
477
536
  }
478
- build_package_lock.rename(configs_package_lock);
537
+ build_bun_lockb.rename(configs_bun_lockb);
479
538
  }
480
539
  }
481
540
  }
@@ -543,6 +602,9 @@ export default defineConfig({{
543
602
  plugins: [react()],
544
603
  root: buildDir,
545
604
  publicDir: false,
605
+ build: {{
606
+ sourcemap: true, // Enable source maps for better error messages
607
+ }},
546
608
  server: {{
547
609
  watch: {{
548
610
  usePolling: true,
@@ -565,11 +627,15 @@ export default defineConfig({{
565
627
  target: "http://localhost:{api_port}",
566
628
  changeOrigin: true,
567
629
  }},
630
+ "/static": {{
631
+ target: "http://localhost:{api_port}",
632
+ changeOrigin: true,
633
+ }},
568
634
  }},
569
635
  }},
570
636
  resolve: {{
571
637
  alias: {{
572
- "@jac-client/utils": path.resolve(buildDir, "{compiled_utils_relative}"),
638
+ "@jac/runtime": path.resolve(buildDir, "{compiled_utils_relative}"),
573
639
  "@jac-client/assets": path.resolve(buildDir, "{compiled_assets_relative}"),
574
640
  }},
575
641
  extensions: [{extensions_str}],
@@ -628,6 +694,13 @@ def _toml_config_to_js(config_name: str, config_data: dict) -> str {
628
694
  impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
629
695
  import sys;
630
696
  import time;
697
+ import from jac_client.plugin.utils { ensure_bun_available }
698
+ # Ensure bun is available before starting dev server
699
+ if not ensure_bun_available() {
700
+ raise ClientBundleError(
701
+ 'Bun is required for dev server. Install manually: https://bun.sh'
702
+ ) from None ;
703
+ }
631
704
  build_dir = self._get_client_dir();
632
705
  node_modules = build_dir / 'node_modules';
633
706
  # Create/update index.html for dev server (load from compiled/ for HMR)
@@ -652,36 +725,25 @@ impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
652
725
  if not generated_package_json.exists() {
653
726
  self.create_package_json();
654
727
  }
655
- # Temporarily copy package.json for npm install
728
+ # Temporarily copy package.json for bun install
656
729
  build_package_json = build_dir / 'package.json';
657
730
  if not build_package_json.exists() {
658
731
  shutil.copy2(generated_package_json, build_package_json);
659
732
  }
660
733
  try {
661
- print(
662
- " ⏳ Installing npm dependencies (this may take a minute)...",
663
- flush=True
664
- );
734
+ print("\n ⏳ Installing dependencies...\n", flush=True);
665
735
  start_time = time.time();
666
736
  result = subprocess.run(
667
- ['npm', 'install', '--progress'],
668
- cwd=build_dir,
669
- check=False,
670
- capture_output=True,
671
- text=True
737
+ ['bun', 'install'], cwd=build_dir, check=False, text=True
672
738
  );
673
739
  elapsed = time.time() - start_time;
674
740
  if result.returncode != 0 {
675
741
  print(
676
- f"\n ✖ npm install failed after {elapsed:.1f}s", file=sys.stderr
742
+ f"\n ✖ bun install failed after {elapsed:.1f}s", file=sys.stderr
677
743
  );
678
- print(f" Error: {result.stderr or result.stdout}", file=sys.stderr);
679
- raise ClientBundleError(
680
- f"Failed to install npm dependencies: {result.stderr
681
- or result.stdout}"
682
- ) ;
744
+ raise ClientBundleError("Failed to install dependencies") ;
683
745
  }
684
- print(f" ✔ Dependencies installed ({elapsed:.1f}s)", flush=True);
746
+ print(f"\n ✔ Dependencies installed ({elapsed:.1f}s)", flush=True);
685
747
  } finally {
686
748
  # Clean up temp package.json
687
749
  if build_package_json.exists() {
@@ -700,7 +762,7 @@ impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
700
762
  logger.debug(f"Starting Vite dev server on port {port}");
701
763
  # Start Vite in dev mode (let output go to terminal for HMR visibility)
702
764
  process = subprocess.Popen(
703
- ['npx', 'vite', '--config', str(config_rel), '--port', str(port)],
765
+ ['bun', 'x', 'vite', '--config', str(config_rel), '--port', str(port)],
704
766
  cwd=build_dir
705
767
  );
706
768
  return process;
@@ -1297,17 +1297,14 @@ def _check_and_install_tauri_cli -> None {
1297
1297
  }
1298
1298
  } except Exception { }
1299
1299
 
1300
- # Check if npm tauri CLI is available
1301
- npm_tauri_ok = False;
1300
+ # Check if bun tauri CLI is available (global install)
1301
+ bun_tauri_ok = False;
1302
1302
  try {
1303
1303
  result = subprocess.run(
1304
- ["npm", "list", "-g", "@tauri-apps/cli"],
1305
- capture_output=True,
1306
- text=True,
1307
- timeout=5
1304
+ ["bun", "pm", "ls", "-g"], capture_output=True, text=True, timeout=5
1308
1305
  );
1309
- if result.returncode == 0 {
1310
- console.print(" ✔ Tauri CLI found (npm)", style="success");
1306
+ if result.returncode == 0 and "@tauri-apps/cli" in result.stdout {
1307
+ console.print(" ✔ Tauri CLI found (bun)", style="success");
1311
1308
  return;
1312
1309
  }
1313
1310
  } except Exception { }
@@ -1327,22 +1324,22 @@ def _check_and_install_tauri_cli -> None {
1327
1324
  cargo_available = True;
1328
1325
  } except Exception { }
1329
1326
 
1330
- # Check if npm is available
1331
- npm_available = False;
1327
+ # Check if bun is available
1328
+ bun_available = False;
1332
1329
  try {
1333
1330
  subprocess.run(
1334
- ["npm", "--version"], capture_output=True, check=True, timeout=5
1331
+ ["bun", "--version"], capture_output=True, check=True, timeout=5
1335
1332
  );
1336
- npm_available = True;
1333
+ bun_available = True;
1337
1334
  } except Exception { }
1338
1335
 
1339
- if not cargo_available and not npm_available {
1336
+ if not cargo_available and not bun_available {
1340
1337
  console.print(
1341
- " Neither cargo nor npm is available. Cannot install Tauri CLI automatically.",
1338
+ " Neither cargo nor bun is available. Cannot install Tauri CLI automatically.",
1342
1339
  style="muted"
1343
1340
  );
1344
1341
  console.print(
1345
- " Please install Rust (for cargo) or Node.js (for npm) first.",
1342
+ " Please install Rust (for cargo) or Bun (https://bun.sh) first.",
1346
1343
  style="muted"
1347
1344
  );
1348
1345
  return;
@@ -1371,11 +1368,11 @@ def _check_and_install_tauri_cli -> None {
1371
1368
  console.warning(f" Error installing via cargo: {e}");
1372
1369
  }
1373
1370
  }
1374
- if npm_available {
1375
- console.print(" Installing Tauri CLI via npm...", style="muted");
1371
+ if bun_available {
1372
+ console.print(" Installing Tauri CLI via bun...", style="muted");
1376
1373
  try {
1377
1374
  result = subprocess.run(
1378
- ["npm", "install", "-g", "@tauri-apps/cli"],
1375
+ ["bun", "add", "-g", "@tauri-apps/cli"],
1379
1376
  check=False,
1380
1377
  timeout=300,
1381
1378
  capture_output=False # Show output
@@ -1384,18 +1381,18 @@ def _check_and_install_tauri_cli -> None {
1384
1381
  console.print(" ✔ Tauri CLI installed", style="success");
1385
1382
  return;
1386
1383
  } else {
1387
- console.warning(" Failed to install via npm");
1384
+ console.warning(" Failed to install via bun");
1388
1385
  }
1389
1386
  } except Exception as e {
1390
- console.warning(f" Error installing via npm: {e}");
1387
+ console.warning(f" Error installing via bun: {e}");
1391
1388
  }
1392
1389
  }
1393
1390
  console.print(" Please install manually:", style="muted");
1394
1391
  if cargo_available {
1395
1392
  console.print(" cargo install tauri-cli", style="muted");
1396
1393
  }
1397
- if npm_available {
1398
- console.print(" npm install -g @tauri-apps/cli", style="muted");
1394
+ if bun_available {
1395
+ console.print(" bun add -g @tauri-apps/cli", style="muted");
1399
1396
  }
1400
1397
  } else {
1401
1398
  console.print(" Skipping Tauri CLI installation.", style="muted");
@@ -1405,8 +1402,8 @@ def _check_and_install_tauri_cli -> None {
1405
1402
  if cargo_available {
1406
1403
  console.print(" cargo install tauri-cli", style="muted");
1407
1404
  }
1408
- if npm_available {
1409
- console.print(" npm install -g @tauri-apps/cli", style="muted");
1405
+ if bun_available {
1406
+ console.print(" bun add -g @tauri-apps/cli", style="muted");
1410
1407
  }
1411
1408
  }
1412
1409
  }
@@ -1846,22 +1843,22 @@ def _run_tauri_build(tauri_dir: Path, platform: Optional[str] = None) -> Path {
1846
1843
  target = "x86_64-unknown-linux-gnu";
1847
1844
  }
1848
1845
 
1849
- # Build command
1850
- build_cmd = ["npm", "run", "tauri", "build"];
1846
+ # Build command - prefer cargo tauri build, fallback to bun if package.json has tauri scripts
1847
+ build_cmd = ["cargo", "tauri", "build"];
1851
1848
  if target {
1852
- build_cmd.extend(["--", "--client", target]);
1849
+ build_cmd.extend(["--target", target]);
1853
1850
  }
1854
1851
 
1855
- # Check if package.json has tauri scripts, if not, use cargo directly
1852
+ # Check if package.json has tauri scripts, use bun run if so
1856
1853
  package_json = tauri_dir.parent / "package.json";
1857
- use_npm = False;
1854
+ use_bun = False;
1858
1855
  if package_json.exists() {
1859
1856
  try {
1860
1857
  with open(package_json, "r") as f {
1861
1858
  package_data = json.load(f);
1862
1859
  scripts = package_data.get("scripts", {});
1863
1860
  if "tauri" in scripts or "tauri:build" in scripts {
1864
- use_npm = True;
1861
+ use_bun = True;
1865
1862
  }
1866
1863
  }
1867
1864
  } except Exception {
@@ -1869,11 +1866,19 @@ def _run_tauri_build(tauri_dir: Path, platform: Optional[str] = None) -> Path {
1869
1866
  }
1870
1867
  }
1871
1868
 
1872
- if not use_npm {
1873
- # Use cargo tauri build directly
1874
- build_cmd = ["cargo", "tauri", "build"];
1869
+ if use_bun {
1870
+ # Ensure bun is available
1871
+ import from jac_client.plugin.utils { ensure_bun_available }
1872
+ if not ensure_bun_available() {
1873
+ console.error(
1874
+ "Bun is required for this project. Install manually: https://bun.sh"
1875
+ );
1876
+ raise RuntimeError("Bun is required") from None ;
1877
+ }
1878
+ # Use bun run tauri build
1879
+ build_cmd = ["bun", "run", "tauri", "build"];
1875
1880
  if target {
1876
- build_cmd.extend(["--client", target]);
1881
+ build_cmd.extend(["--", "--target", target]);
1877
1882
  }
1878
1883
  }
1879
1884
 
@@ -2210,32 +2215,40 @@ def _run_tauri_dev(tauri_dir: Path) -> subprocess.Popen {
2210
2215
  ) {
2211
2216
  console.warning("Tauri CLI not found.");
2212
2217
  console.print(" Install manually: cargo install tauri-cli", style="muted");
2213
- console.print(" Or use npm: npm install -D @tauri-apps/cli", style="muted");
2218
+ console.print(" Or use bun: bun add -g @tauri-apps/cli", style="muted");
2214
2219
  raise RuntimeError(
2215
2220
  "Tauri CLI not installed. Install it first:\n"
2216
2221
  " cargo install tauri-cli\n"
2217
- " Or: npm install -D @tauri-apps/cli"
2222
+ " Or: bun add -g @tauri-apps/cli"
2218
2223
  ) ;
2219
2224
  }
2220
2225
 
2221
2226
  # Check if package.json has tauri scripts
2222
2227
  package_json = tauri_dir.parent / "package.json";
2223
- use_npm = False;
2228
+ use_bun = False;
2224
2229
  if package_json.exists() {
2225
2230
  try {
2226
2231
  with open(package_json, "r") as f {
2227
2232
  package_data = json.load(f);
2228
2233
  scripts = package_data.get("scripts", {});
2229
2234
  if "tauri" in scripts or "tauri:dev" in scripts {
2230
- use_npm = True;
2235
+ use_bun = True;
2231
2236
  }
2232
2237
  }
2233
2238
  } except Exception { }
2234
2239
  }
2235
2240
 
2236
- if use_npm {
2237
- # Use npm run tauri dev
2238
- dev_cmd = ["npm", "run", "tauri", "dev"];
2241
+ if use_bun {
2242
+ # Ensure bun is available
2243
+ import from jac_client.plugin.utils { ensure_bun_available }
2244
+ if not ensure_bun_available() {
2245
+ console.error(
2246
+ "Bun is required for this project. Install manually: https://bun.sh"
2247
+ );
2248
+ raise RuntimeError("Bun is required") from None ;
2249
+ }
2250
+ # Use bun run tauri dev
2251
+ dev_cmd = ["bun", "run", "tauri", "dev"];
2239
2252
  } else {
2240
2253
  # Use cargo tauri dev directly
2241
2254
  dev_cmd = ["cargo", "tauri", "dev"];
@@ -1 +1,4 @@
1
1
  """Utility modules for jac-client plugin."""
2
+
3
+ import from .bun_installer { ensure_bun_available, prompt_install_bun }
4
+ import from .client_deps { ensure_client_deps }
@@ -0,0 +1,16 @@
1
+ """Bun installer utility for jac-client.
2
+
3
+ Provides functions to check for Bun availability and prompt for installation.
4
+ """
5
+
6
+ """Check if bun is available, add to PATH if needed, or prompt to install.
7
+
8
+ Returns True if bun is available (or was successfully installed), False otherwise.
9
+ """
10
+ def ensure_bun_available -> bool;
11
+
12
+ """Prompt user to install Bun and install if confirmed.
13
+
14
+ Returns True if installation succeeded, False otherwise.
15
+ """
16
+ def prompt_install_bun -> bool;
@@ -0,0 +1,14 @@
1
+ """Client dependency checker utility for jac-client.
2
+
3
+ Provides a function to check if required npm dependencies are configured
4
+ and prompt the user to install defaults if missing.
5
+ """
6
+
7
+ """Check if client npm dependencies are configured, prompt to install if missing.
8
+
9
+ Takes a JacClientConfig instance, checks if dependencies and devDependencies
10
+ are both empty, and if so prompts the user to install default jac-client deps.
11
+
12
+ Returns True if deps are configured (or were just installed), False if user declined.
13
+ """
14
+ def ensure_client_deps(config_loader: object) -> bool;