jac-client 0.2.8__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/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 +542 -0
  17. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +26 -12
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +43 -12
  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 +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 +114 -119
  23. jac_client/examples/all-in-one/pages/nestedDemo.jac +44 -51
  24. jac_client/examples/all-in-one/pages/notFound.jac +15 -21
  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 +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 +491 -411
  54. jac_client/plugin/client.jac +25 -0
  55. jac_client/plugin/client_runtime.cl.jac +10 -4
  56. jac_client/plugin/impl/client.impl.jac +96 -55
  57. jac_client/plugin/impl/client_runtime.impl.jac +155 -1
  58. jac_client/plugin/plugin_config.jac +211 -29
  59. jac_client/plugin/src/__init__.jac +0 -2
  60. jac_client/plugin/src/compiler.jac +0 -1
  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 +49 -17
  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 +191 -64
  69. jac_client/plugin/src/targets/desktop/sidecar/main.py +144 -0
  70. jac_client/plugin/src/targets/desktop_target.jac +37 -0
  71. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +2347 -0
  72. jac_client/plugin/src/targets/impl/registry.impl.jac +64 -0
  73. jac_client/plugin/src/targets/impl/web_target.impl.jac +157 -0
  74. jac_client/plugin/src/targets/register.jac +21 -0
  75. jac_client/plugin/src/targets/registry.jac +87 -0
  76. jac_client/plugin/src/targets/web_target.jac +35 -0
  77. jac_client/plugin/src/vite_bundler.jac +6 -0
  78. jac_client/plugin/utils/__init__.jac +3 -0
  79. jac_client/plugin/utils/bun_installer.jac +16 -0
  80. jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
  81. jac_client/templates/client.jacpack +72 -0
  82. jac_client/templates/fullstack.jacpack +61 -0
  83. jac_client/tests/conftest.py +103 -47
  84. jac_client/tests/fixtures/spawn_test/app.jac +49 -52
  85. jac_client/tests/fixtures/with-ts/app.jac +27 -27
  86. jac_client/tests/test_cli.py +182 -71
  87. jac_client/tests/test_e2e.py +232 -0
  88. jac_client/tests/test_helpers.py +58 -0
  89. jac_client/tests/test_it.py +91 -135
  90. jac_client/tests/test_it_desktop.py +891 -0
  91. {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/METADATA +6 -6
  92. jac_client-0.2.11.dist-info/RECORD +113 -0
  93. {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/WHEEL +1 -1
  94. jac_client/examples/all-in-one/app.jac +0 -573
  95. jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +0 -70
  96. jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +0 -552
  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 -371
  102. jac_client/examples/basic-auth-with-router/src/app.jac +0 -464
  103. jac_client/examples/basic-full-stack/src/app.jac +0 -359
  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 -89
  117. jac_client-0.2.8.dist-info/RECORD +0 -97
  118. {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/entry_points.txt +0 -0
  119. {jac_client-0.2.8.dist-info → jac_client-0.2.11.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 (
@@ -78,6 +74,8 @@ impl ViteBundler.create_package_json(
78
74
  }
79
75
  # Generate tsconfig.json during build time
80
76
  self.create_tsconfig();
77
+ # Generate config files (postcss, tailwind, etc.) from jac.toml
78
+ self.create_config_files();
81
79
  return package_json_path;
82
80
  }
83
81
 
@@ -129,22 +127,22 @@ impl ViteBundler.create_tsconfig(self: ViteBundler) -> Path {
129
127
  }
130
128
 
131
129
  """
132
- Clean up root package.json and move package-lock.json to configs/.
130
+ Clean up root package.json and move bun.lockb to configs/.
133
131
 
134
- Remove root package.json and move package-lock.json to configs/.
132
+ Remove root package.json and move bun.lockb to configs/.
135
133
  """
136
134
  impl ViteBundler._cleanup_root_package_files(self: ViteBundler) -> None {
137
135
  root_package_json = self.project_dir / 'package.json';
138
- root_package_lock = self.project_dir / 'package-lock.json';
136
+ root_bun_lockb = self.project_dir / 'bun.lockb';
139
137
  build_dir = self._get_client_dir();
140
138
  configs_dir = build_dir / 'configs';
141
- configs_package_lock = configs_dir / 'package-lock.json';
142
- if root_package_lock.exists() {
139
+ configs_bun_lockb = configs_dir / 'bun.lockb';
140
+ if root_bun_lockb.exists() {
143
141
  configs_dir.mkdir(exist_ok=True);
144
- if configs_package_lock.exists() {
145
- configs_package_lock.unlink();
142
+ if configs_bun_lockb.exists() {
143
+ configs_bun_lockb.unlink();
146
144
  }
147
- shutil.move(str(root_package_lock), str(configs_package_lock));
145
+ shutil.move(str(root_bun_lockb), str(configs_bun_lockb));
148
146
  }
149
147
  if root_package_json.exists() {
150
148
  root_package_json.unlink();
@@ -152,7 +150,7 @@ impl ViteBundler._cleanup_root_package_files(self: ViteBundler) -> None {
152
150
  }
153
151
 
154
152
  """
155
- Ensure root package.json exists temporarily for npm commands.
153
+ Ensure root package.json exists temporarily for bun commands.
156
154
 
157
155
  Create root package.json temporarily if it doesn't exist.
158
156
  """
@@ -301,26 +299,76 @@ impl ViteBundler.create_vite_config(self: ViteBundler, entry_file: Path) -> Path
301
299
  else '';
302
300
  config_content = f'''import {{ defineConfig }} from "vite";
303
301
  import path from "path";
302
+ import fs from "fs";
304
303
  import {{ fileURLToPath }} from "url";
305
304
  {imports_section}const __dirname = path.dirname(fileURLToPath(import.meta.url));
306
305
  // Config is in configs/ inside .jac/client/, so go up one level to .jac/client/, then up two more to project root
307
306
  const buildDir = path.resolve(__dirname, "..");
308
307
  const projectRoot = path.resolve(__dirname, "../../..");
309
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
+
310
352
  /**
311
353
  * Vite configuration generated from config.json (in project root)
312
354
  * To customize, edit config.json instead of this file.
313
355
  */
314
356
 
315
357
  export default defineConfig({{
316
- plugins: [{(newline + plugins_str + newline + ' ') if plugins_str else ''}],
358
+ plugins: [
359
+ jacSourceMapper(),{(newline + plugins_str + newline + ' ') if plugins_str else ''}],
317
360
  root: buildDir, // base folder (.jac/client/) so vite can find node_modules
318
361
  build: {{
362
+ sourcemap: true, // Enable source maps for better error messages
319
363
  rollupOptions: {{
320
364
  input: path.resolve(buildDir, "{entry_relative}"), // your compiled entry file
321
365
  output: {{
322
366
  entryFileNames: "client.[hash].js", // name of the final js file
323
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
+ }},
324
372
  }},
325
373
  }},
326
374
  outDir: path.resolve(buildDir, "{output_relative}"), // final bundled output
@@ -330,7 +378,7 @@ export default defineConfig({{
330
378
  publicDir: false,
331
379
  {server_section} resolve: {{
332
380
  alias: {{
333
- "@jac-client/utils": path.resolve(buildDir, "{compiled_utils_relative}"),
381
+ "@jac/runtime": path.resolve(buildDir, "{compiled_utils_relative}"),
334
382
  "@jac-client/assets": path.resolve(buildDir, "{compiled_assets_relative}"),
335
383
  }},
336
384
  extensions: [{extensions_str}],
@@ -375,6 +423,14 @@ impl ViteBundler.find_bundle(self: ViteBundler) -> Optional[Path] {
375
423
 
376
424
  """Run Vite build with generated config in .jac/client/configs/."""
377
425
  impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) -> None {
426
+ import sys;
427
+ import time;
428
+ import shutil;
429
+ import from jac_client.plugin.utils { ensure_bun_available }
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
+ }
378
434
  self.output_dir.mkdir(parents=True, exist_ok=True);
379
435
  generated_package_json = self._get_client_dir() / 'configs' / 'package.json';
380
436
  if not generated_package_json.exists() {
@@ -387,7 +443,7 @@ impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) ->
387
443
  build_dir = self._get_client_dir();
388
444
  node_modules = build_dir / 'node_modules';
389
445
  if not node_modules.exists() {
390
- # Temporarily copy package.json to client build dir for npm install
446
+ # Temporarily copy package.json to client build dir for bun install
391
447
  build_package_json = build_dir / 'package.json';
392
448
  configs_package_json = build_dir / 'configs' / 'package.json';
393
449
  if configs_package_json.exists() and not build_package_json.exists() {
@@ -395,65 +451,78 @@ impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) ->
395
451
  shutil.copy2(configs_package_json, build_package_json);
396
452
  }
397
453
  try {
398
- # Install to .jac/client/node_modules
399
- subprocess.run(
400
- ['npm', 'install'],
401
- cwd=build_dir,
402
- check=True,
403
- capture_output=True,
404
- text=True
454
+ # Install to .jac/client/node_modules with progress feedback
455
+ print("\n ⏳ Installing dependencies...\n", flush=True);
456
+ start_time = time.time();
457
+ result = subprocess.run(
458
+ ['bun', 'install'], cwd=build_dir, check=False, text=True
405
459
  );
406
- } except subprocess.CalledProcessError as e {
407
- raise e from ClientBundleError(
408
- f"Failed to install npm dependencies: {e.stderr}"
409
- ) ;
460
+ elapsed = time.time() - start_time;
461
+ if result.returncode != 0 {
462
+ print(
463
+ f"\n ✖ bun install failed after {elapsed:.1f}s",
464
+ file=sys.stderr
465
+ );
466
+ raise ClientBundleError("Failed to install dependencies") ;
467
+ }
468
+ print(f"\n ✔ Dependencies installed ({elapsed:.1f}s)", flush=True);
410
469
  } except FileNotFoundError {
411
- raise None from ClientBundleError(
412
- 'npm command not found. Ensure Node.js and npm are installed.'
413
- ) ;
470
+ # This shouldn't happen since we check for bun at the start
471
+ raise ClientBundleError(
472
+ 'Bun command not found. Install Bun: https://bun.sh'
473
+ ) from None ;
414
474
  }
415
475
  }
416
476
  if self.config_path {
417
477
  # Make config path relative to build_dir (where vite runs from)
418
478
  try {
419
479
  config_rel = self.config_path.relative_to(build_dir);
420
- command = ['npx', 'vite', 'build', '--config', str(config_rel)];
480
+ command = ['bun', 'x', 'vite', 'build', '--config', str(config_rel)];
421
481
  } except ValueError {
422
482
  # Config is outside client build dir, use absolute path
423
- command = ['npx', 'vite', 'build', '--config', str(self.config_path)];
483
+ command = [
484
+ 'bun',
485
+ 'x',
486
+ 'vite',
487
+ 'build',
488
+ '--config',
489
+ str(self.config_path)
490
+ ];
424
491
  }
425
492
  } elif entry_file {
426
493
  generated_config = self.create_vite_config(entry_file);
427
494
  # Config is in configs/, make it relative to build_dir
428
495
  config_rel = generated_config.relative_to(build_dir);
429
- command = ['npx', 'vite', 'build', '--config', str(config_rel)];
496
+ command = ['bun', 'x', 'vite', 'build', '--config', str(config_rel)];
430
497
  } else {
431
- command = ['npm', 'run', 'build'];
498
+ command = ['bun', 'run', 'build'];
432
499
  }
433
500
  # Run vite from client build directory so it can find node_modules
434
- result = subprocess.run(
435
- command, cwd=build_dir, check=False, capture_output=True, text=True
436
- );
501
+ print("\n ⏳ Building client bundle...\n", flush=True);
502
+ start_time = time.time();
503
+ result = subprocess.run(command, cwd=build_dir, check=False, text=True);
504
+ elapsed = time.time() - start_time;
437
505
  if result.returncode != 0 {
438
- error_msg = result.stderr or result.stdout or 'Unknown error';
506
+ print(f"\n ✖ Vite build failed after {elapsed:.1f}s", file=sys.stderr);
439
507
  raise ClientBundleError(
440
- f"Vite build failed:\n{error_msg}\nCommand: {' '.join(command)}"
508
+ f"Vite build failed (see output above)\nCommand: {' '.join(command)}"
441
509
  ) from None ;
442
510
  }
511
+ print(f"\n ✔ Client bundle built ({elapsed:.1f}s)", flush=True);
443
512
  } finally {
444
513
  # Clean up temporary package.json in client build dir
445
514
  build_package_json = build_dir / 'package.json';
446
515
  if build_package_json.exists() {
447
516
  build_package_json.unlink();
448
517
  }
449
- # Move package-lock.json to configs/ if it exists
450
- build_package_lock = build_dir / 'package-lock.json';
451
- if build_package_lock.exists() {
452
- configs_package_lock = build_dir / 'configs' / 'package-lock.json';
453
- if configs_package_lock.exists() {
454
- configs_package_lock.unlink();
518
+ # Move bun.lockb to configs/ if it exists
519
+ build_bun_lockb = build_dir / 'bun.lockb';
520
+ if build_bun_lockb.exists() {
521
+ configs_bun_lockb = build_dir / 'configs' / 'bun.lockb';
522
+ if configs_bun_lockb.exists() {
523
+ configs_bun_lockb.unlink();
455
524
  }
456
- build_package_lock.rename(configs_package_lock);
525
+ build_bun_lockb.rename(configs_bun_lockb);
457
526
  }
458
527
  }
459
528
  }
@@ -521,6 +590,9 @@ export default defineConfig({{
521
590
  plugins: [react()],
522
591
  root: buildDir,
523
592
  publicDir: false,
593
+ build: {{
594
+ sourcemap: true, // Enable source maps for better error messages
595
+ }},
524
596
  server: {{
525
597
  watch: {{
526
598
  usePolling: true,
@@ -547,7 +619,7 @@ export default defineConfig({{
547
619
  }},
548
620
  resolve: {{
549
621
  alias: {{
550
- "@jac-client/utils": path.resolve(buildDir, "{compiled_utils_relative}"),
622
+ "@jac/runtime": path.resolve(buildDir, "{compiled_utils_relative}"),
551
623
  "@jac-client/assets": path.resolve(buildDir, "{compiled_assets_relative}"),
552
624
  }},
553
625
  extensions: [{extensions_str}],
@@ -558,8 +630,61 @@ export default defineConfig({{
558
630
  return config_path;
559
631
  }
560
632
 
633
+ """Create config files from jac.toml [plugins.client.configs].
634
+
635
+ Generates JavaScript config files (e.g., postcss.config.js, tailwind.config.js)
636
+ from TOML configuration. Each key in [plugins.client.configs] becomes a config file.
637
+
638
+ Example jac.toml:
639
+ [plugins.client.configs.postcss]
640
+ plugins = ["tailwindcss", "autoprefixer"]
641
+
642
+ [plugins.client.configs.tailwind]
643
+ content = ["./src/**/*.{js,jsx}"]
644
+
645
+ This generates:
646
+ - .jac/client/configs/postcss.config.js
647
+ - .jac/client/configs/tailwind.config.js
648
+ """
649
+ impl ViteBundler.create_config_files(self: ViteBundler) -> list[Path] {
650
+ configs = self.config_loader.get_configs();
651
+ if not configs {
652
+ return [];
653
+ }
654
+ build_dir = self._get_client_dir();
655
+ configs_dir = build_dir / 'configs';
656
+ configs_dir.mkdir(parents=True, exist_ok=True);
657
+ created_files: list[Path] = [];
658
+ for (config_name, config_data) in configs.items() {
659
+ config_path = configs_dir / f'{config_name}.config.js';
660
+
661
+ # Convert the TOML config to JavaScript module.exports
662
+ js_content = _toml_config_to_js(config_name, config_data);
663
+ config_path.write_text(js_content, encoding='utf-8');
664
+ created_files.append(config_path);
665
+ }
666
+ return created_files;
667
+ }
668
+
669
+ """Convert TOML config data to JavaScript config file content.
670
+
671
+ Generates a generic module.exports with the config data as JSON.
672
+ """
673
+ def _toml_config_to_js(config_name: str, config_data: dict) -> str {
674
+ return f"module.exports = {json.dumps(config_data, indent=2)};\n";
675
+ }
676
+
561
677
  """Start Vite dev server as a subprocess."""
562
678
  impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
679
+ import sys;
680
+ import time;
681
+ import from jac_client.plugin.utils { ensure_bun_available }
682
+ # Ensure bun is available before starting dev server
683
+ if not ensure_bun_available() {
684
+ raise ClientBundleError(
685
+ 'Bun is required for dev server. Install manually: https://bun.sh'
686
+ ) from None ;
687
+ }
563
688
  build_dir = self._get_client_dir();
564
689
  node_modules = build_dir / 'node_modules';
565
690
  # Create/update index.html for dev server (load from compiled/ for HMR)
@@ -584,23 +709,25 @@ impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
584
709
  if not generated_package_json.exists() {
585
710
  self.create_package_json();
586
711
  }
587
- # Temporarily copy package.json for npm install
712
+ # Temporarily copy package.json for bun install
588
713
  build_package_json = build_dir / 'package.json';
589
714
  if not build_package_json.exists() {
590
715
  shutil.copy2(generated_package_json, build_package_json);
591
716
  }
592
717
  try {
593
- print("[Vite] Installing dependencies...");
594
- subprocess.run(
595
- ['npm', 'install'],
596
- cwd=build_dir,
597
- check=True,
598
- capture_output=True,
599
- text=True
718
+ print("\n ⏳ Installing dependencies...\n", flush=True);
719
+ start_time = time.time();
720
+ result = subprocess.run(
721
+ ['bun', 'install'], cwd=build_dir, check=False, text=True
600
722
  );
601
- } except subprocess.CalledProcessError as e {
602
- print(f"[Vite] Error installing dependencies: {e.stderr}");
603
- raise ;
723
+ elapsed = time.time() - start_time;
724
+ if result.returncode != 0 {
725
+ print(
726
+ f"\n ✖ bun install failed after {elapsed:.1f}s", file=sys.stderr
727
+ );
728
+ raise ClientBundleError("Failed to install dependencies") ;
729
+ }
730
+ print(f"\n ✔ Dependencies installed ({elapsed:.1f}s)", flush=True);
604
731
  } finally {
605
732
  # Clean up temp package.json
606
733
  if build_package_json.exists() {
@@ -616,10 +743,10 @@ impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
616
743
  ) ;
617
744
  }
618
745
  config_rel = dev_config.relative_to(build_dir);
619
- print(f"[Vite] Starting dev server on port {port}...");
746
+ logger.debug(f"Starting Vite dev server on port {port}");
620
747
  # Start Vite in dev mode (let output go to terminal for HMR visibility)
621
748
  process = subprocess.Popen(
622
- ['npx', 'vite', '--config', str(config_rel), '--port', str(port)],
749
+ ['bun', 'x', 'vite', '--config', str(config_rel), '--port', str(port)],
623
750
  cwd=build_dir
624
751
  );
625
752
  return process;
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Jac Sidecar Entry Point
4
+
5
+ This is the entry point for the Jac backend sidecar.
6
+ It launches the Jac runtime and starts an HTTP API server.
7
+
8
+ Usage:
9
+ python -m jac_client.plugin.src.targets.desktop.sidecar.main [OPTIONS]
10
+ # Or via wrapper script: ./jac-sidecar.sh [OPTIONS]
11
+
12
+ Options:
13
+ --module-path PATH Path to the .jac module file (default: main.jac)
14
+ --port PORT Port to bind the API server (default: 8000)
15
+ --base-path PATH Base path for the project (default: current directory)
16
+ --host HOST Host to bind to (default: 127.0.0.1)
17
+ --help Show this help message
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ from jaclang.cli.console import console
27
+
28
+
29
+ def main():
30
+ """Main entry point for the sidecar."""
31
+ parser = argparse.ArgumentParser(
32
+ description="Jac Backend Sidecar - Runs Jac API server in a bundled executable"
33
+ )
34
+ parser.add_argument(
35
+ "--module-path",
36
+ type=str,
37
+ default="main.jac",
38
+ help="Path to the .jac module file (default: main.jac)",
39
+ )
40
+ parser.add_argument(
41
+ "--port",
42
+ type=int,
43
+ default=8000,
44
+ help="Port to bind the API server (default: 8000)",
45
+ )
46
+ parser.add_argument(
47
+ "--base-path",
48
+ type=str,
49
+ default=None,
50
+ help="Base path for the project (default: current directory)",
51
+ )
52
+ parser.add_argument(
53
+ "--host",
54
+ type=str,
55
+ default="127.0.0.1",
56
+ help="Host to bind to (default: 127.0.0.1)",
57
+ )
58
+
59
+ args = parser.parse_args()
60
+
61
+ # Determine base path
62
+ if args.base_path:
63
+ base_path = Path(args.base_path).resolve()
64
+ else:
65
+ # Try to find project root (look for jac.toml)
66
+ base_path = Path.cwd()
67
+ for parent in [base_path] + list(base_path.parents):
68
+ if (parent / "jac.toml").exists():
69
+ base_path = parent
70
+ break
71
+
72
+ # Resolve module path
73
+ module_path = Path(args.module_path)
74
+ if not module_path.is_absolute():
75
+ module_path = base_path / module_path
76
+
77
+ if not module_path.exists():
78
+ console.print(f"Error: Module file not found: {module_path}", file=sys.stderr)
79
+ console.print(f" Base path: {base_path}", file=sys.stderr)
80
+ sys.exit(1)
81
+
82
+ # Extract module name (without .jac extension)
83
+ module_name = module_path.stem
84
+ module_base = module_path.parent
85
+
86
+ # Import Jac runtime and server
87
+ try:
88
+ # Import jaclang (must be installed via pip)
89
+ from jaclang.pycore.runtime import JacRuntime as Jac
90
+ except ImportError as e:
91
+ console.print(f"Error: Failed to import Jac runtime: {e}", file=sys.stderr)
92
+ console.print(
93
+ " Make sure jaclang is installed: pip install jaclang", file=sys.stderr
94
+ )
95
+ sys.exit(1)
96
+
97
+ # Initialize Jac runtime
98
+ try:
99
+ # Import the module
100
+ Jac.jac_import(target=module_name, base_path=str(module_base), lng="jac")
101
+ if Jac.program.errors_had:
102
+ console.print("Error: Failed to compile module:", file=sys.stderr)
103
+ for error in Jac.program.errors_had:
104
+ console.print(f" {error}", file=sys.stderr)
105
+ sys.exit(1)
106
+ except Exception as e:
107
+ console.print(
108
+ f"Error: Failed to load module '{module_name}': {e}", file=sys.stderr
109
+ )
110
+ import traceback
111
+
112
+ traceback.print_exc()
113
+ sys.exit(1)
114
+
115
+ # Create and start the API server
116
+ try:
117
+ # Get server class (allows plugins like jac-scale to provide enhanced server)
118
+ server_class = Jac.get_api_server_class()
119
+ server = server_class(
120
+ module_name=module_name, port=args.port, base_path=str(base_path)
121
+ )
122
+
123
+ console.print("Jac Sidecar starting...")
124
+ console.print(f" Module: {module_name}")
125
+ console.print(f" Base path: {base_path}")
126
+ console.print(f" Server: http://{args.host}:{args.port}")
127
+ console.print("\nPress Ctrl+C to stop the server\n")
128
+
129
+ # Start the server (blocks until interrupted)
130
+ server.start(dev=False)
131
+
132
+ except KeyboardInterrupt:
133
+ console.print("\nShutting down sidecar...")
134
+ sys.exit(0)
135
+ except Exception as e:
136
+ console.print(f"Error: Server failed to start: {e}", file=sys.stderr)
137
+ import traceback
138
+
139
+ traceback.print_exc()
140
+ sys.exit(1)
141
+
142
+
143
+ if __name__ == "__main__":
144
+ main()
@@ -0,0 +1,37 @@
1
+ """Desktop target implementation.
2
+
3
+ This target will be implemented in Phase 2.
4
+ """
5
+ import from pathlib { Path }
6
+ import from typing { Optional }
7
+ import from jac_client.plugin.src.targets.registry { ClientTarget }
8
+
9
+ """Desktop build target (placeholder for Phase 2)."""
10
+ class DesktopTarget(ClientTarget) {
11
+ def init(self: DesktopTarget) {
12
+ self.name = "desktop";
13
+ self.default = False;
14
+ self.requires_setup = True;
15
+ self.config_section = "desktop";
16
+ self.output_dir = Path("src-tauri/target/release/bundle");
17
+ }
18
+
19
+ """Setup desktop target - scaffold Tauri project structure."""
20
+ override def setup(self: DesktopTarget, project_dir: Path) -> None;
21
+
22
+ """Build desktop app - build web bundle first, then wrap with Tauri."""
23
+ override def build(
24
+ self: DesktopTarget,
25
+ entry_file: Path,
26
+ project_dir: Path,
27
+ platform: Optional[str] = None
28
+ ) -> Path;
29
+
30
+ """Start desktop dev server - start web dev server and launch tauri dev."""
31
+ override def dev(self: DesktopTarget, entry_file: Path, project_dir: Path) -> None;
32
+
33
+ """Start desktop app - build web bundle and launch Tauri with built bundle."""
34
+ override def start(
35
+ self: DesktopTarget, entry_file: Path, project_dir: Path
36
+ ) -> None;
37
+ }