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.
- jac_client/examples/all-in-one/{src/button.jac → button.jac} +4 -3
- jac_client/examples/all-in-one/components/CategoryFilter.jac +47 -0
- jac_client/examples/all-in-one/components/Header.jac +17 -0
- jac_client/examples/all-in-one/components/ProfitOverview.jac +64 -0
- jac_client/examples/all-in-one/components/Summary.jac +76 -0
- jac_client/examples/all-in-one/components/TransactionForm.jac +188 -0
- jac_client/examples/all-in-one/components/TransactionItem.jac +62 -0
- jac_client/examples/all-in-one/components/TransactionList.jac +44 -0
- jac_client/examples/all-in-one/components/button.jac +8 -0
- jac_client/examples/all-in-one/components/navigation.jac +126 -0
- jac_client/examples/all-in-one/constants/categories.jac +36 -0
- jac_client/examples/all-in-one/constants/clients.jac +12 -0
- jac_client/examples/all-in-one/context/BudgetContext.jac +31 -0
- jac_client/examples/all-in-one/hooks/useBudget.jac +122 -0
- jac_client/examples/all-in-one/hooks/useLocalStorage.jac +37 -0
- jac_client/examples/all-in-one/main.jac +542 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.jac +140 -0
- jac_client/examples/all-in-one/pages/FeaturesTest.jac +157 -0
- jac_client/examples/all-in-one/pages/LandingPage.jac +124 -0
- jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +65 -0
- jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +675 -0
- jac_client/examples/all-in-one/pages/loginPage.jac +127 -0
- jac_client/examples/all-in-one/pages/nestedDemo.jac +54 -0
- jac_client/examples/all-in-one/pages/notFound.jac +18 -0
- jac_client/examples/all-in-one/pages/signupPage.jac +127 -0
- jac_client/examples/all-in-one/utils/formatters.jac +49 -0
- jac_client/examples/asset-serving/css-with-image/main.jac +92 -0
- jac_client/examples/asset-serving/image-asset/main.jac +56 -0
- jac_client/examples/asset-serving/import-alias/main.jac +109 -0
- jac_client/examples/basic/main.jac +23 -0
- jac_client/examples/basic-auth/main.jac +363 -0
- jac_client/examples/basic-auth-with-router/main.jac +451 -0
- jac_client/examples/basic-full-stack/main.jac +362 -0
- jac_client/examples/css-styling/js-styling/main.jac +63 -0
- jac_client/examples/css-styling/material-ui/main.jac +122 -0
- jac_client/examples/css-styling/pure-css/main.jac +55 -0
- jac_client/examples/css-styling/sass-example/main.jac +55 -0
- jac_client/examples/css-styling/styled-components/main.jac +62 -0
- jac_client/examples/css-styling/tailwind-example/main.jac +74 -0
- jac_client/examples/full-stack-with-auth/main.jac +696 -0
- jac_client/examples/little-x/main.jac +681 -0
- jac_client/examples/little-x/src/submit-button.jac +15 -14
- jac_client/examples/nested-folders/nested-advance/main.jac +26 -0
- jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +4 -6
- jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +9 -13
- jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +29 -32
- jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +12 -18
- jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +7 -5
- jac_client/examples/nested-folders/nested-basic/src/button.jac +4 -3
- jac_client/examples/nested-folders/nested-basic/src/components/button.jac +4 -3
- jac_client/examples/ts-support/main.jac +35 -0
- jac_client/examples/with-router/main.jac +286 -0
- jac_client/plugin/cli.jac +507 -470
- jac_client/plugin/client.jac +30 -12
- jac_client/plugin/client_runtime.cl.jac +25 -15
- jac_client/plugin/impl/client.impl.jac +126 -26
- jac_client/plugin/impl/client_runtime.impl.jac +182 -10
- jac_client/plugin/plugin_config.jac +216 -34
- jac_client/plugin/src/__init__.jac +0 -2
- jac_client/plugin/src/compiler.jac +2 -2
- jac_client/plugin/src/config_loader.jac +1 -0
- jac_client/plugin/src/desktop_config.jac +31 -0
- jac_client/plugin/src/impl/compiler.impl.jac +99 -30
- jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
- jac_client/plugin/src/impl/desktop_config.impl.jac +191 -0
- jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
- jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
- jac_client/plugin/src/impl/vite_bundler.impl.jac +384 -144
- jac_client/plugin/src/package_installer.jac +1 -1
- jac_client/plugin/src/targets/desktop/sidecar/main.py +144 -0
- jac_client/plugin/src/targets/desktop_target.jac +37 -0
- jac_client/plugin/src/targets/impl/desktop_target.impl.jac +2347 -0
- jac_client/plugin/src/targets/impl/registry.impl.jac +64 -0
- jac_client/plugin/src/targets/impl/web_target.impl.jac +157 -0
- jac_client/plugin/src/targets/register.jac +21 -0
- jac_client/plugin/src/targets/registry.jac +87 -0
- jac_client/plugin/src/targets/web_target.jac +35 -0
- jac_client/plugin/src/vite_bundler.jac +15 -1
- jac_client/plugin/utils/__init__.jac +3 -0
- jac_client/plugin/utils/bun_installer.jac +16 -0
- jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
- jac_client/templates/client.jacpack +72 -0
- jac_client/templates/fullstack.jacpack +61 -0
- jac_client/tests/conftest.py +110 -52
- jac_client/tests/fixtures/spawn_test/app.jac +64 -70
- jac_client/tests/fixtures/with-ts/app.jac +28 -28
- jac_client/tests/test_cli.py +280 -113
- jac_client/tests/test_e2e.py +232 -0
- jac_client/tests/test_helpers.py +58 -0
- jac_client/tests/test_it.py +325 -154
- jac_client/tests/test_it_desktop.py +891 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/METADATA +20 -11
- jac_client-0.2.11.dist-info/RECORD +113 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/WHEEL +1 -1
- jac_client/examples/all-in-one/src/app.jac +0 -841
- jac_client/examples/all-in-one/src/components/button.jac +0 -7
- jac_client/examples/asset-serving/css-with-image/src/app.jac +0 -88
- jac_client/examples/asset-serving/image-asset/src/app.jac +0 -55
- jac_client/examples/asset-serving/import-alias/src/app.jac +0 -111
- jac_client/examples/basic/src/app.jac +0 -21
- jac_client/examples/basic-auth/src/app.jac +0 -377
- jac_client/examples/basic-auth-with-router/src/app.jac +0 -464
- jac_client/examples/basic-full-stack/src/app.jac +0 -365
- jac_client/examples/css-styling/js-styling/src/app.jac +0 -84
- jac_client/examples/css-styling/material-ui/src/app.jac +0 -122
- jac_client/examples/css-styling/pure-css/src/app.jac +0 -64
- jac_client/examples/css-styling/sass-example/src/app.jac +0 -64
- jac_client/examples/css-styling/styled-components/src/app.jac +0 -71
- jac_client/examples/css-styling/tailwind-example/src/app.jac +0 -63
- jac_client/examples/full-stack-with-auth/src/app.jac +0 -722
- jac_client/examples/little-x/src/app.jac +0 -719
- jac_client/examples/nested-folders/nested-advance/src/app.jac +0 -35
- jac_client/examples/ts-support/src/app.jac +0 -35
- jac_client/examples/with-router/src/app.jac +0 -323
- jac_client/plugin/src/babel_processor.jac +0 -18
- jac_client/plugin/src/impl/babel_processor.impl.jac +0 -84
- jac_client-0.2.6.dist-info/RECORD +0 -74
- {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/entry_points.txt +0 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Implementation of target registry."""
|
|
2
|
+
|
|
3
|
+
"""Register a new build target."""
|
|
4
|
+
impl TargetRegistry.register(target: ClientTarget) -> None {
|
|
5
|
+
self._targets[target.name] = target;
|
|
6
|
+
if target.default {
|
|
7
|
+
self._default_target = target.name;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
"""Get a target by name."""
|
|
12
|
+
impl TargetRegistry.get(name: str) -> Optional[ClientTarget] {
|
|
13
|
+
return self._targets.get(name);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
"""Get the default target."""
|
|
17
|
+
impl TargetRegistry.get_default -> Optional[ClientTarget] {
|
|
18
|
+
if self._default_target {
|
|
19
|
+
return self._targets.get(self._default_target);
|
|
20
|
+
}
|
|
21
|
+
# Find first target marked as default
|
|
22
|
+
for target in self._targets.values() {
|
|
23
|
+
if target.default {
|
|
24
|
+
return target;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return None;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
"""Get all registered targets."""
|
|
31
|
+
impl TargetRegistry.get_all -> list[ClientTarget] {
|
|
32
|
+
return list(self._targets.values());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
"""Check if a target is registered."""
|
|
36
|
+
impl TargetRegistry.has(name: str) -> bool {
|
|
37
|
+
return name in self._targets;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
"""Set the default target."""
|
|
41
|
+
impl TargetRegistry.set_default(name: str) -> None {
|
|
42
|
+
if name not in self._targets {
|
|
43
|
+
raise ValueError(f"ClientTarget '{name}' is not registered") ;
|
|
44
|
+
}
|
|
45
|
+
# Unset previous default
|
|
46
|
+
if self._default_target {
|
|
47
|
+
prev_target = self._targets.get(self._default_target);
|
|
48
|
+
if prev_target {
|
|
49
|
+
prev_target.default = False;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
# Set new default
|
|
53
|
+
self._default_target = name;
|
|
54
|
+
target = self._targets.get(name);
|
|
55
|
+
if target {
|
|
56
|
+
target.default = True;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
"""Clear all registered targets (for testing)."""
|
|
61
|
+
impl TargetRegistry.clear -> None {
|
|
62
|
+
self._targets.clear();
|
|
63
|
+
self._default_target = None;
|
|
64
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Implementation of WebTarget methods."""
|
|
2
|
+
import types;
|
|
3
|
+
import from pathlib { Path }
|
|
4
|
+
import from typing { Optional }
|
|
5
|
+
import from jac_client.plugin.src.vite_bundler { ViteBundler }
|
|
6
|
+
|
|
7
|
+
"""Build web bundle using existing Vite pipeline."""
|
|
8
|
+
impl WebTarget.build(
|
|
9
|
+
self: WebTarget,
|
|
10
|
+
entry_file: Path,
|
|
11
|
+
project_dir: Path,
|
|
12
|
+
platform: Optional[str] = None
|
|
13
|
+
) -> Path {
|
|
14
|
+
import os;
|
|
15
|
+
import shutil;
|
|
16
|
+
import from jaclang.pycore.runtime { JacRuntime as Jac }
|
|
17
|
+
import from jac_client.plugin.client { JacClient }
|
|
18
|
+
bundler = ViteBundler(project_dir=project_dir);
|
|
19
|
+
# Clean dist directory for fresh build
|
|
20
|
+
dist_dir = bundler.output_dir;
|
|
21
|
+
if dist_dir.exists() {
|
|
22
|
+
shutil.rmtree(dist_dir);
|
|
23
|
+
}
|
|
24
|
+
dist_dir.mkdir(parents=True, exist_ok=True);
|
|
25
|
+
# Load the module (same as jac start does via _proc_file + jac_import)
|
|
26
|
+
(base, mod) = os.path.split(str(entry_file));
|
|
27
|
+
base = base or "./";
|
|
28
|
+
if entry_file.name.endswith('.jac') {
|
|
29
|
+
mod = mod[:-4];
|
|
30
|
+
}
|
|
31
|
+
# Import the module (Jac.jac_import handles context creation internally)
|
|
32
|
+
Jac.jac_import(target=mod, base_path=base, lng='jac');
|
|
33
|
+
if Jac.program.errors_had {
|
|
34
|
+
errors = '\n'.join(Jac.program.errors_had);
|
|
35
|
+
raise RuntimeError(f"Failed to compile {entry_file}:\n{errors}") ;
|
|
36
|
+
}
|
|
37
|
+
# Get the loaded module
|
|
38
|
+
loaded_mod = Jac.loaded_modules.get(mod);
|
|
39
|
+
if not loaded_mod {
|
|
40
|
+
raise RuntimeError(f"Module '{mod}' not found after import") ;
|
|
41
|
+
}
|
|
42
|
+
# Type assertion: loaded_mod is guaranteed to be not None here
|
|
43
|
+
if not isinstance(loaded_mod, types.ModuleType) {
|
|
44
|
+
raise RuntimeError(f"Module '{mod}' is not a valid module type") ;
|
|
45
|
+
}
|
|
46
|
+
# Build bundle using ViteClientBundleBuilder (same as jac start's introspector.ensure_bundle)
|
|
47
|
+
builder = JacClient.get_client_bundle_builder();
|
|
48
|
+
bundle = builder.build(loaded_mod, force=True);
|
|
49
|
+
# Find the bundle file
|
|
50
|
+
bundle_path = bundler.find_bundle();
|
|
51
|
+
if not bundle_path {
|
|
52
|
+
raise RuntimeError("Web build failed: bundle not found") ;
|
|
53
|
+
}
|
|
54
|
+
# Always generate static index.html for static serving
|
|
55
|
+
_generate_index_html(
|
|
56
|
+
bundle_path=bundle_path,
|
|
57
|
+
bundler=bundler,
|
|
58
|
+
loaded_mod=loaded_mod,
|
|
59
|
+
project_dir=project_dir
|
|
60
|
+
);
|
|
61
|
+
return bundle_path;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
"""Generate static index.html file for the built bundle."""
|
|
65
|
+
def _generate_index_html(
|
|
66
|
+
bundle_path: Path,
|
|
67
|
+
bundler: ViteBundler,
|
|
68
|
+
loaded_mod: types.ModuleType,
|
|
69
|
+
project_dir: Path
|
|
70
|
+
) -> None {
|
|
71
|
+
import html;
|
|
72
|
+
import hashlib;
|
|
73
|
+
import from jaclang.project.config { get_config }
|
|
74
|
+
import from jac_client.plugin.client { HeaderBuilder }
|
|
75
|
+
import from jaclang.pycore.runtime { JacRuntime as Jac }
|
|
76
|
+
|
|
77
|
+
# Get the first client export function (or default to 'app')
|
|
78
|
+
mod_path = getattr(loaded_mod, '__file__', None);
|
|
79
|
+
if mod_path {
|
|
80
|
+
mod = Jac.program.mod.hub.get(mod_path);
|
|
81
|
+
if mod and mod.gen.client_manifest {
|
|
82
|
+
client_exports = mod.gen.client_manifest.exports;
|
|
83
|
+
# client_exports is a list[str], not a dict
|
|
84
|
+
if client_exports and len(client_exports) > 0 {
|
|
85
|
+
function_name = client_exports[0];
|
|
86
|
+
} else {
|
|
87
|
+
function_name = 'app';
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
function_name = 'app';
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
function_name = 'app';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Get module name
|
|
97
|
+
module_name = loaded_mod.__name__;
|
|
98
|
+
|
|
99
|
+
# Get dist directory (output_dir is already a Path)
|
|
100
|
+
dist_dir = Path(bundler.output_dir);
|
|
101
|
+
|
|
102
|
+
# Check for CSS file
|
|
103
|
+
css_link = '';
|
|
104
|
+
css_file = bundler.find_css();
|
|
105
|
+
if css_file {
|
|
106
|
+
css_hash = hashlib.sha256(css_file.read_bytes()).hexdigest()[:8];
|
|
107
|
+
css_filename = css_file.name;
|
|
108
|
+
css_link = f'<link rel="stylesheet" href="{css_filename}?hash={css_hash}"/>';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Get meta data from config
|
|
112
|
+
config = get_config();
|
|
113
|
+
meta_data = {};
|
|
114
|
+
if config {
|
|
115
|
+
client_cfg = config.get_plugin_config("client");
|
|
116
|
+
if client_cfg {
|
|
117
|
+
meta_data = client_cfg.get("app_meta_data", {});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Build HTML head using HeaderBuilder (same as render_page)
|
|
122
|
+
head_builder = HeaderBuilder(meta_data, function_name);
|
|
123
|
+
head_content = head_builder.build_head();
|
|
124
|
+
if css_link {
|
|
125
|
+
head_content += f"\n {css_link}";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Get bundle filename (relative to dist_dir)
|
|
129
|
+
bundle_filename = bundle_path.name;
|
|
130
|
+
|
|
131
|
+
# Generate HTML with __jac_init__ script tag for client runtime
|
|
132
|
+
init_payload = {
|
|
133
|
+
"module": module_name,
|
|
134
|
+
"function": function_name,
|
|
135
|
+
"args": {},
|
|
136
|
+
"argOrder": [],
|
|
137
|
+
"globals": {}
|
|
138
|
+
};
|
|
139
|
+
import json;
|
|
140
|
+
init_json = json.dumps(init_payload);
|
|
141
|
+
|
|
142
|
+
html_content = f'''<!DOCTYPE html>
|
|
143
|
+
<html lang="en">
|
|
144
|
+
<head>
|
|
145
|
+
{head_content}
|
|
146
|
+
</head>
|
|
147
|
+
<body>
|
|
148
|
+
<div id="root"></div>
|
|
149
|
+
<script id="__jac_init__" type="application/json">{html.escape(init_json)}</script>
|
|
150
|
+
<script src="{bundle_filename}" defer></script>
|
|
151
|
+
</body>
|
|
152
|
+
</html>''';
|
|
153
|
+
|
|
154
|
+
# Write index.html to dist directory
|
|
155
|
+
index_html = dist_dir / 'index.html';
|
|
156
|
+
index_html.write_text(html_content, encoding='utf-8');
|
|
157
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Register all build targets.
|
|
2
|
+
|
|
3
|
+
This module registers all available build targets when the plugin loads.
|
|
4
|
+
"""
|
|
5
|
+
import from jac_client.plugin.src.targets.registry { get_target_registry }
|
|
6
|
+
import from jac_client.plugin.src.targets.web_target { WebTarget }
|
|
7
|
+
import from jac_client.plugin.src.targets.desktop_target { DesktopTarget }
|
|
8
|
+
# Note: Jac's annex pass should auto-discover .impl.jac files automatically
|
|
9
|
+
# The impl files in impl/ directory should be loaded when the base classes are imported
|
|
10
|
+
"""Register all available targets."""
|
|
11
|
+
def register_targets -> None {
|
|
12
|
+
registry = get_target_registry();
|
|
13
|
+
|
|
14
|
+
# Register web target (default)
|
|
15
|
+
web_target = WebTarget();
|
|
16
|
+
registry.register(web_target);
|
|
17
|
+
|
|
18
|
+
# Register desktop target (placeholder for Phase 2)
|
|
19
|
+
desktop_target = DesktopTarget();
|
|
20
|
+
registry.register(desktop_target);
|
|
21
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Target registry for build targets (web, desktop, mobile, etc.).
|
|
2
|
+
|
|
3
|
+
This module provides a registry system for managing different build targets.
|
|
4
|
+
Each target is a class that inherits from the base ClientTarget class.
|
|
5
|
+
"""
|
|
6
|
+
import from typing { Optional }
|
|
7
|
+
import from pathlib { Path }
|
|
8
|
+
|
|
9
|
+
"""Base class for all build targets.
|
|
10
|
+
|
|
11
|
+
All targets must inherit from this class and implement the required methods.
|
|
12
|
+
"""
|
|
13
|
+
class ClientTarget {
|
|
14
|
+
has name: str,
|
|
15
|
+
default: bool = False,
|
|
16
|
+
requires_setup: bool = False,
|
|
17
|
+
config_section: str = "",
|
|
18
|
+
required_dependencies: list[str] = [],
|
|
19
|
+
output_dir: Optional[Path] = None;
|
|
20
|
+
|
|
21
|
+
"""Setup the target (one-time initialization)."""
|
|
22
|
+
def setup(self: ClientTarget, project_dir: Path) -> None abs;
|
|
23
|
+
|
|
24
|
+
"""Build the target."""
|
|
25
|
+
def build(
|
|
26
|
+
self: ClientTarget,
|
|
27
|
+
entry_file: Path,
|
|
28
|
+
project_dir: Path,
|
|
29
|
+
platform: Optional[str] = None
|
|
30
|
+
) -> Path abs;
|
|
31
|
+
|
|
32
|
+
"""Start dev server for the target."""
|
|
33
|
+
def dev(self: ClientTarget, entry_file: Path, project_dir: Path) -> None abs;
|
|
34
|
+
|
|
35
|
+
"""Start the target (production mode - build and run)."""
|
|
36
|
+
def start(self: ClientTarget, entry_file: Path, project_dir: Path) -> None abs;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
"""Central registry for build targets.
|
|
40
|
+
|
|
41
|
+
This is a singleton that collects all build targets registered by plugins.
|
|
42
|
+
The CLI uses this registry to delegate build operations to the appropriate target.
|
|
43
|
+
"""
|
|
44
|
+
obj TargetRegistry {
|
|
45
|
+
has _targets: dict[str, ClientTarget] = {},
|
|
46
|
+
_default_target: Optional[str] = None;
|
|
47
|
+
|
|
48
|
+
"""Register a new build target."""
|
|
49
|
+
def register(target: ClientTarget) -> None;
|
|
50
|
+
|
|
51
|
+
"""Get a target by name."""
|
|
52
|
+
def get(name: str) -> Optional[ClientTarget];
|
|
53
|
+
|
|
54
|
+
"""Get the default target."""
|
|
55
|
+
def get_default -> Optional[ClientTarget];
|
|
56
|
+
|
|
57
|
+
"""Get all registered targets."""
|
|
58
|
+
def get_all -> list[ClientTarget];
|
|
59
|
+
|
|
60
|
+
"""Check if a target is registered."""
|
|
61
|
+
def has(name: str) -> bool;
|
|
62
|
+
|
|
63
|
+
"""Set the default target."""
|
|
64
|
+
def set_default(name: str) -> None;
|
|
65
|
+
|
|
66
|
+
"""Clear all registered targets (for testing)."""
|
|
67
|
+
def clear -> None;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Global singleton instance
|
|
71
|
+
glob _registry: TargetRegistry | None = None;
|
|
72
|
+
|
|
73
|
+
"""Get or create the global target registry."""
|
|
74
|
+
def get_target_registry -> TargetRegistry {
|
|
75
|
+
import from jac_client.plugin.src.targets.registry { TargetRegistry }
|
|
76
|
+
global _registry;
|
|
77
|
+
if _registry is None {
|
|
78
|
+
_registry = TargetRegistry();
|
|
79
|
+
}
|
|
80
|
+
return _registry;
|
|
81
|
+
}
|
|
82
|
+
# Target name: "web", "desktop", "android", "ios"
|
|
83
|
+
# Is this the default target?
|
|
84
|
+
# Does this target need setup?
|
|
85
|
+
# Section in jac.toml (e.g., "desktop")
|
|
86
|
+
# Required npm/system dependencies
|
|
87
|
+
# Where build outputs go
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Web target implementation.
|
|
2
|
+
|
|
3
|
+
This is the default target for building web applications.
|
|
4
|
+
"""
|
|
5
|
+
import from pathlib { Path }
|
|
6
|
+
import from typing { Optional }
|
|
7
|
+
import from jac_client.plugin.src.targets.registry { ClientTarget }
|
|
8
|
+
|
|
9
|
+
"""Web build target."""
|
|
10
|
+
class WebTarget(ClientTarget) {
|
|
11
|
+
def init(self: WebTarget) {
|
|
12
|
+
self.name = "web";
|
|
13
|
+
self.default = True;
|
|
14
|
+
self.requires_setup = False;
|
|
15
|
+
self.config_section = "plugin.client";
|
|
16
|
+
self.output_dir = Path(".jac/client/dist");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
"""Setup web target (no-op for web, already set up by default)."""
|
|
20
|
+
override def setup(self: WebTarget, project_dir: Path) -> None;
|
|
21
|
+
|
|
22
|
+
"""Build web bundle using existing Vite pipeline."""
|
|
23
|
+
override def build(
|
|
24
|
+
self: WebTarget,
|
|
25
|
+
entry_file: Path,
|
|
26
|
+
project_dir: Path,
|
|
27
|
+
platform: Optional[str] = None
|
|
28
|
+
) -> Path;
|
|
29
|
+
|
|
30
|
+
"""Start web dev server."""
|
|
31
|
+
override def dev(self: WebTarget, entry_file: Path, project_dir: Path) -> None;
|
|
32
|
+
|
|
33
|
+
"""Start web target (no-op for web, handled by jac start)."""
|
|
34
|
+
override def start(self: WebTarget, entry_file: Path, project_dir: Path) -> None;
|
|
35
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"""Vite bundling module."""
|
|
2
2
|
import hashlib;
|
|
3
3
|
import json;
|
|
4
|
+
import logging;
|
|
4
5
|
import shutil;
|
|
5
6
|
import subprocess;
|
|
6
7
|
import from pathlib { Path }
|
|
7
|
-
import from typing { Optional }
|
|
8
|
+
import from typing { Any, Optional }
|
|
8
9
|
import from jaclang.runtimelib.client_bundle { ClientBundleError }
|
|
9
10
|
import from .config_loader { JacClientConfig }
|
|
11
|
+
|
|
12
|
+
glob logger = logging.getLogger(__name__);
|
|
10
13
|
"""Handles Vite bundling operations."""
|
|
11
14
|
class ViteBundler {
|
|
12
15
|
def init(
|
|
@@ -17,6 +20,7 @@ class ViteBundler {
|
|
|
17
20
|
config_path: Optional[Path] = None
|
|
18
21
|
);
|
|
19
22
|
|
|
23
|
+
def _get_client_dir(self: ViteBundler) -> Path;
|
|
20
24
|
def build(self: ViteBundler, entry_file: Optional[Path] = None) -> None;
|
|
21
25
|
def find_bundle(self: ViteBundler) -> Optional[Path];
|
|
22
26
|
def find_css(self: ViteBundler) -> Optional[Path];
|
|
@@ -33,4 +37,14 @@ class ViteBundler {
|
|
|
33
37
|
) -> Path;
|
|
34
38
|
|
|
35
39
|
def create_tsconfig(self: ViteBundler) -> Path;
|
|
40
|
+
"""Create config files from jac.toml [plugins.client.configs]."""
|
|
41
|
+
def create_config_files(self: ViteBundler) -> list[Path];
|
|
42
|
+
|
|
43
|
+
"""Create a dev-mode vite config with API proxy for HMR."""
|
|
44
|
+
def create_dev_vite_config(
|
|
45
|
+
self: ViteBundler, entry_file: Path, api_port: int = 8000
|
|
46
|
+
) -> Path;
|
|
47
|
+
|
|
48
|
+
"""Start Vite dev server as a subprocess."""
|
|
49
|
+
def start_dev_server(self: ViteBundler, port: int = 3000) -> Any;
|
|
36
50
|
}
|
|
@@ -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,99 @@
|
|
|
1
|
+
"""Implementation of Bun installer utility."""
|
|
2
|
+
|
|
3
|
+
"""Check if bun is available, add to PATH if needed, or prompt to install."""
|
|
4
|
+
impl ensure_bun_available -> bool {
|
|
5
|
+
import os;
|
|
6
|
+
import shutil;
|
|
7
|
+
# Check if bun is already in PATH
|
|
8
|
+
if shutil.which("bun") {
|
|
9
|
+
return True;
|
|
10
|
+
}
|
|
11
|
+
# Check if bun exists at ~/.bun/bin but not in PATH
|
|
12
|
+
bun_bin = os.path.expanduser("~/.bun/bin");
|
|
13
|
+
bun_path = os.path.join(bun_bin, "bun");
|
|
14
|
+
if os.path.exists(bun_path) {
|
|
15
|
+
# Add to PATH for current process
|
|
16
|
+
current_path = os.environ.get("PATH", "");
|
|
17
|
+
os.environ["PATH"] = f"{bun_bin}:{current_path}";
|
|
18
|
+
return True;
|
|
19
|
+
}
|
|
20
|
+
# Bun not found - prompt to install
|
|
21
|
+
return prompt_install_bun();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
"""Prompt user to install Bun and install if confirmed."""
|
|
25
|
+
impl prompt_install_bun -> bool {
|
|
26
|
+
import os;
|
|
27
|
+
import subprocess;
|
|
28
|
+
import sys;
|
|
29
|
+
import shutil;
|
|
30
|
+
print("\n ⚠ Bun is required but not installed.", file=sys.stderr);
|
|
31
|
+
print(
|
|
32
|
+
" Bun is a fast JavaScript runtime used for package management and bundling.",
|
|
33
|
+
file=sys.stderr
|
|
34
|
+
);
|
|
35
|
+
print(" Learn more: https://bun.sh\n", file=sys.stderr);
|
|
36
|
+
try {
|
|
37
|
+
response = input(" Install Bun now? [Y/n]: ").strip().lower();
|
|
38
|
+
} except (EOFError, KeyboardInterrupt) {
|
|
39
|
+
print("", file=sys.stderr);
|
|
40
|
+
return False;
|
|
41
|
+
}
|
|
42
|
+
if response and response not in ('y', 'yes', '') {
|
|
43
|
+
return False;
|
|
44
|
+
}
|
|
45
|
+
print("\n ⏳ Installing Bun...", flush=True);
|
|
46
|
+
try {
|
|
47
|
+
# Check if curl is available
|
|
48
|
+
subprocess.run(
|
|
49
|
+
["curl", "--version"], capture_output=True, check=True, timeout=5
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
# Install Bun via official script
|
|
53
|
+
result = subprocess.run(
|
|
54
|
+
["sh", "-c", "curl -fsSL https://bun.sh/install | bash"],
|
|
55
|
+
timeout=120,
|
|
56
|
+
check=False
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if result.returncode == 0 {
|
|
60
|
+
print(" ✔ Bun installed successfully!", flush=True);
|
|
61
|
+
# Add Bun to PATH for current session
|
|
62
|
+
bun_bin = os.path.expanduser("~/.bun/bin");
|
|
63
|
+
if os.path.exists(bun_bin) {
|
|
64
|
+
current_path = os.environ.get("PATH", "");
|
|
65
|
+
os.environ["PATH"] = f"{bun_bin}:{current_path}";
|
|
66
|
+
# Verify installation
|
|
67
|
+
if shutil.which("bun") {
|
|
68
|
+
verify = subprocess.run(
|
|
69
|
+
["bun", "--version"], capture_output=True, text=True
|
|
70
|
+
);
|
|
71
|
+
if verify.returncode == 0 {
|
|
72
|
+
print(f" ✔ Verified: Bun {verify.stdout.strip()}", flush=True);
|
|
73
|
+
return True;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
print(
|
|
78
|
+
" ⚠ Bun installed but may require terminal restart.", file=sys.stderr
|
|
79
|
+
);
|
|
80
|
+
print(" Run: source ~/.bashrc (or restart terminal)", file=sys.stderr);
|
|
81
|
+
return True;
|
|
82
|
+
} else {
|
|
83
|
+
print(" ✖ Bun installation failed.", file=sys.stderr);
|
|
84
|
+
return False;
|
|
85
|
+
}
|
|
86
|
+
} except subprocess.TimeoutExpired {
|
|
87
|
+
print(" ✖ Installation timed out.", file=sys.stderr);
|
|
88
|
+
return False;
|
|
89
|
+
} except FileNotFoundError {
|
|
90
|
+
print(
|
|
91
|
+
" ✖ curl not found. Please install Bun manually: https://bun.sh",
|
|
92
|
+
file=sys.stderr
|
|
93
|
+
);
|
|
94
|
+
return False;
|
|
95
|
+
} except Exception as e {
|
|
96
|
+
print(f" ✖ Installation failed: {e}", file=sys.stderr);
|
|
97
|
+
return False;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "client",
|
|
3
|
+
"description": "Jac project with frontend client setup",
|
|
4
|
+
"config": {
|
|
5
|
+
"project": {
|
|
6
|
+
"name": "{{name}}",
|
|
7
|
+
"version": "1.0.0",
|
|
8
|
+
"description": "Jac client application: {{name}}",
|
|
9
|
+
"entry-point": "main.jac"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {},
|
|
12
|
+
"dependencies.npm": {
|
|
13
|
+
"jac-client-node": "1.0.4"
|
|
14
|
+
},
|
|
15
|
+
"dependencies.npm.dev": {
|
|
16
|
+
"@jac-client/dev-deps": "1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"dev-dependencies": {
|
|
19
|
+
"watchdog": ">=3.0.0"
|
|
20
|
+
},
|
|
21
|
+
"serve": {
|
|
22
|
+
"base_route_app": "app"
|
|
23
|
+
},
|
|
24
|
+
"plugins.client": {}
|
|
25
|
+
},
|
|
26
|
+
"files": {
|
|
27
|
+
"main.jac": "\"\"\"Main entry point for {{name}}.\"\"\"\n\n# Client-side imports (useState is auto-injected when using `has` variables)\ncl import from react { useEffect }\ncl import from .components.Button { Button }\n\n# Client-side component\ncl {\n def:pub app() -> any {\n has count: int = 0;\n\n useEffect(lambda -> None {\n console.log(\"Count updated:\", count);\n }, [count]);\n\n return <div style={{padding: \"2rem\", fontFamily: \"Arial, sans-serif\"}}>\n <h1>Hello, World!</h1>\n <p>Count: {count}</p>\n <div style={{display: \"flex\", gap: \"1rem\", marginTop: \"1rem\"}}>\n <Button\n label=\"Increment\"\n onClick={lambda -> None { count = count + 1; }}\n variant=\"primary\"\n />\n <Button\n label=\"Reset\"\n onClick={lambda -> None { count = 0; }}\n variant=\"secondary\"\n />\n </div>\n </div>;\n }\n}\n",
|
|
28
|
+
"components/Button.cl.jac": "\"\"\"Button component for the Jac client application.\"\"\"\n\ndef:pub Button(label: str, onClick: any, variant: str = \"primary\", disabled: bool = False) -> any {\n base_styles = {\n \"padding\": \"0.75rem 1.5rem\",\n \"fontSize\": \"1rem\",\n \"fontWeight\": \"600\",\n \"borderRadius\": \"0.5rem\",\n \"border\": \"none\",\n \"cursor\": \"not-allowed\" if disabled else \"pointer\",\n \"transition\": \"all 0.2s ease\"\n };\n\n variant_styles = {\n \"primary\": {\n \"backgroundColor\": \"#9ca3af\" if disabled else \"#3b82f6\",\n \"color\": \"#ffffff\"\n },\n \"secondary\": {\n \"backgroundColor\": \"#e5e7eb\" if disabled else \"#6b7280\",\n \"color\": \"#ffffff\"\n }\n };\n\n return <button\n style={{**base_styles, **variant_styles[variant]}}\n onClick={onClick}\n disabled={disabled}\n >\n {label}\n </button>;\n}\n",
|
|
29
|
+
"README.md": "# {{name}}\n\nA Jac client-side application with React support.\n\n## Project Structure\n\n```\n{{name}}/\n\u251c\u2500\u2500 jac.toml # Project configuration\n\u251c\u2500\u2500 main.jac # Main application entry\n\u251c\u2500\u2500 components/ # Reusable components\n\u2502 \u2514\u2500\u2500 Button.cl.jac # Example Jac component\n\u251c\u2500\u2500 assets/ # Static assets (images, fonts, etc.)\n\u2514\u2500\u2500 build/ # Build output (generated)\n```\n\n## Getting Started\n\nStart the development server:\n\n```bash\njac start main.jac\n```\n\n## Components\n\nCreate Jac components in `components/` as `.cl.jac` files and import them:\n\n```jac\ncl import from .components.Button { Button }\n```\n\n## Adding Dependencies\n\nAdd npm packages with the --cl flag:\n\n```bash\njac add --cl react-router-dom\n```\n"
|
|
30
|
+
},
|
|
31
|
+
"directories": [
|
|
32
|
+
"components",
|
|
33
|
+
"assets",
|
|
34
|
+
".jac/client"
|
|
35
|
+
],
|
|
36
|
+
"gitignore_entries": [
|
|
37
|
+
"# Ignore all build artifacts in .jac directory",
|
|
38
|
+
"*"
|
|
39
|
+
],
|
|
40
|
+
"root_gitignore_entries": [
|
|
41
|
+
"# Jac project",
|
|
42
|
+
"packages/",
|
|
43
|
+
"*.jbc",
|
|
44
|
+
"*.jir",
|
|
45
|
+
"",
|
|
46
|
+
"# Python",
|
|
47
|
+
"__pycache__/",
|
|
48
|
+
"*.py[cod]",
|
|
49
|
+
".venv/",
|
|
50
|
+
"venv/",
|
|
51
|
+
"",
|
|
52
|
+
"# IDE",
|
|
53
|
+
".idea/",
|
|
54
|
+
".vscode/",
|
|
55
|
+
"*.swp",
|
|
56
|
+
"",
|
|
57
|
+
"# Node.js",
|
|
58
|
+
"node_modules/",
|
|
59
|
+
"",
|
|
60
|
+
"# Build artifacts",
|
|
61
|
+
".jac/",
|
|
62
|
+
"*.session",
|
|
63
|
+
"*.session.*"
|
|
64
|
+
],
|
|
65
|
+
"jaclang": "0.9.8",
|
|
66
|
+
"plugins": [
|
|
67
|
+
{
|
|
68
|
+
"name": "jac-client",
|
|
69
|
+
"version": "0.2.8"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|