jac-client 0.2.8__py3-none-any.whl → 0.2.9__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/{app.jac → main.jac} +5 -5
- jac_client/examples/all-in-one/pages/BudgetPlanner.jac +8 -1
- jac_client/examples/all-in-one/pages/FeaturesTest.jac +16 -1
- jac_client/examples/all-in-one/pages/{FeaturesTest.cl.jac → features_test_ui.cl.jac} +11 -0
- jac_client/examples/all-in-one/pages/nestedDemo.jac +1 -1
- jac_client/examples/all-in-one/pages/notFound.jac +2 -7
- jac_client/plugin/cli.jac +162 -435
- jac_client/plugin/client.jac +25 -0
- jac_client/plugin/client_runtime.cl.jac +5 -1
- jac_client/plugin/impl/client.impl.jac +96 -55
- jac_client/plugin/impl/client_runtime.impl.jac +154 -0
- jac_client/plugin/plugin_config.jac +243 -15
- jac_client/plugin/src/config_loader.jac +1 -0
- jac_client/plugin/src/impl/compiler.impl.jac +1 -1
- jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
- jac_client/plugin/src/impl/vite_bundler.impl.jac +97 -16
- jac_client/plugin/src/vite_bundler.jac +6 -0
- jac_client/plugin/utils/__init__.jac +1 -0
- jac_client/plugin/utils/impl/node_installer.impl.jac +249 -0
- jac_client/plugin/utils/node_installer.jac +41 -0
- jac_client/templates/client.jacpack +72 -0
- jac_client/templates/fullstack.jacpack +61 -0
- jac_client/tests/conftest.py +48 -7
- jac_client/tests/test_cli.py +184 -70
- jac_client/tests/test_e2e.py +232 -0
- jac_client/tests/test_helpers.py +65 -0
- jac_client/tests/test_it.py +91 -135
- {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/METADATA +4 -4
- {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/RECORD +52 -45
- {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/WHEEL +1 -1
- /jac_client/examples/all-in-one/pages/{BudgetPlanner.cl.jac → budget_planner_ui.cl.jac} +0 -0
- /jac_client/examples/asset-serving/css-with-image/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/asset-serving/image-asset/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/asset-serving/import-alias/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/basic/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/basic-auth/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/basic-auth-with-router/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/basic-full-stack/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/css-styling/js-styling/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/css-styling/material-ui/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/css-styling/pure-css/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/css-styling/sass-example/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/css-styling/styled-components/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/css-styling/tailwind-example/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/full-stack-with-auth/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/little-x/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/nested-folders/nested-advance/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/ts-support/{src/app.jac → main.jac} +0 -0
- /jac_client/examples/with-router/{src/app.jac → main.jac} +0 -0
- {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/entry_points.txt +0 -0
- {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/top_level.txt +0 -0
|
@@ -5,10 +5,13 @@ configuration system, registering:
|
|
|
5
5
|
- Plugin metadata (name, version)
|
|
6
6
|
- Config schema for [plugins.client] section
|
|
7
7
|
- npm dependency type for [dependencies.npm] section
|
|
8
|
+
- Project templates (client, fullstack)
|
|
8
9
|
"""
|
|
9
10
|
|
|
11
|
+
import os;
|
|
10
12
|
import subprocess;
|
|
11
13
|
import sys;
|
|
14
|
+
import json;
|
|
12
15
|
import from pathlib { Path }
|
|
13
16
|
import from typing { Any }
|
|
14
17
|
import from jaclang.pycore.runtime { hookimpl }
|
|
@@ -68,6 +71,11 @@ class JacClientPluginConfig {
|
|
|
68
71
|
"type": "dict",
|
|
69
72
|
"default": {},
|
|
70
73
|
"description": "TypeScript configuration overrides"
|
|
74
|
+
},
|
|
75
|
+
"configs": {
|
|
76
|
+
"type": "dict",
|
|
77
|
+
"default": {},
|
|
78
|
+
"description": "Generate config files for npm packages. Keys are config names (e.g., 'postcss', 'tailwind'), values are the config objects to export."
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
};
|
|
@@ -79,15 +87,222 @@ class JacClientPluginConfig {
|
|
|
79
87
|
return {
|
|
80
88
|
"name": "npm",
|
|
81
89
|
"dev_name": "npm.dev",
|
|
82
|
-
"cli_flag": "--
|
|
90
|
+
"cli_flag": "--npm",
|
|
83
91
|
"install_dir": ".jac/client/configs",
|
|
84
92
|
"install_handler": _npm_install_handler,
|
|
85
93
|
"install_all_handler": _npm_install_all_handler,
|
|
86
94
|
"remove_handler": _npm_remove_handler
|
|
87
95
|
};
|
|
88
96
|
}
|
|
97
|
+
|
|
98
|
+
"""Register the client and fullstack project templates."""
|
|
99
|
+
@hookimpl
|
|
100
|
+
static def register_project_template -> list[dict[str, Any]] {
|
|
101
|
+
templates: list[dict[str, Any]] = [];
|
|
102
|
+
|
|
103
|
+
# Load templates from bundled JSON files
|
|
104
|
+
for template_name in ["client", "fullstack"] {
|
|
105
|
+
template = _load_template(template_name);
|
|
106
|
+
if template {
|
|
107
|
+
templates.append(template);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return templates;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# ===============================================================================
|
|
116
|
+
# Template Loader
|
|
117
|
+
# ===============================================================================
|
|
118
|
+
"""Load a template from bundled JSON file."""
|
|
119
|
+
def _load_template(template_name: str) -> dict[str, Any] | None {
|
|
120
|
+
# Find the template JSON file relative to this module
|
|
121
|
+
# plugin_config.jac is in jac_client/plugin/, so .parent.parent = jac_client/
|
|
122
|
+
module_dir = Path(__file__).parent.parent; # jac_client directory
|
|
123
|
+
template_path = module_dir / "templates" / f"{template_name}.jacpack";
|
|
124
|
+
|
|
125
|
+
if not template_path.exists() {
|
|
126
|
+
# Fallback: look in installed package location
|
|
127
|
+
import from importlib.resources { files }
|
|
128
|
+
try {
|
|
129
|
+
package_files = files("jac_client");
|
|
130
|
+
template_path = Path(str(package_files)) / "templates" / f"{template_name}.jacpack";
|
|
131
|
+
} except Exception {
|
|
132
|
+
return None;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if not template_path.exists() {
|
|
137
|
+
return None;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
with open(template_path, "r") as f {
|
|
142
|
+
data = json.load(f);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Attach the post_create hook
|
|
146
|
+
data["post_create"] = _post_create_client;
|
|
147
|
+
|
|
148
|
+
return data;
|
|
149
|
+
} except Exception as e {
|
|
150
|
+
print(
|
|
151
|
+
f"Warning: Could not load {template_name} template: {e}", file=sys.stderr
|
|
152
|
+
);
|
|
153
|
+
return None;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# ===============================================================================
|
|
158
|
+
# Post-create Hook for Client Template
|
|
159
|
+
# ===============================================================================
|
|
160
|
+
"""Post-create hook to ensure Node.js is installed and optionally install npm packages."""
|
|
161
|
+
def _post_create_client(project_path: Path, project_name: str) -> None {
|
|
162
|
+
import from jac_client.plugin.src.vite_bundler { ViteBundler }
|
|
163
|
+
import from jac_client.plugin.utils.node_installer { NodeInstaller }
|
|
164
|
+
import from jaclang.cli.console { console }
|
|
165
|
+
import shutil;
|
|
166
|
+
|
|
167
|
+
# First, ensure Node.js is installed
|
|
168
|
+
console.print();
|
|
169
|
+
with console.status(
|
|
170
|
+
"[cyan]Checking Node.js installation...[/cyan]", spinner="dots"
|
|
171
|
+
) as status {
|
|
172
|
+
node_result = NodeInstaller.ensure_node_installed(interactive=True);
|
|
173
|
+
node_installed = node_result[0];
|
|
174
|
+
message = node_result[1];
|
|
175
|
+
was_just_installed = node_result[2];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if not node_installed {
|
|
179
|
+
console.warning(f"Node.js is required for client development");
|
|
180
|
+
console.print(f"\n {message}", file=sys.stderr);
|
|
181
|
+
console.print(
|
|
182
|
+
" After installing Node.js, run: jac add --cl\n",
|
|
183
|
+
style="muted",
|
|
184
|
+
file=sys.stderr
|
|
185
|
+
);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.print(f" ✔ {message}", style="success");
|
|
190
|
+
|
|
191
|
+
# Check if npm package installation should be skipped
|
|
192
|
+
if os.environ.get("JAC_CLIENT_SKIP_NPM_INSTALL") {
|
|
193
|
+
console.print(
|
|
194
|
+
"\n ⏭ Skipping npm package installation (JAC_CLIENT_SKIP_NPM_INSTALL is set)",
|
|
195
|
+
style="muted"
|
|
196
|
+
);
|
|
197
|
+
console.print(
|
|
198
|
+
" Run 'jac add --npm' when ready to install packages.\n", style="muted"
|
|
199
|
+
);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.print();
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
# Verify jac.toml exists
|
|
207
|
+
toml_path = project_path / "jac.toml";
|
|
208
|
+
if not toml_path.exists() {
|
|
209
|
+
print(
|
|
210
|
+
"Warning: jac.toml not found, skipping package installation",
|
|
211
|
+
file=sys.stderr
|
|
212
|
+
);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Create ViteBundler instance
|
|
217
|
+
bundler = ViteBundler(project_path);
|
|
218
|
+
|
|
219
|
+
# Generate package.json with default packages
|
|
220
|
+
bundler.create_package_json(project_name=project_name);
|
|
221
|
+
|
|
222
|
+
# Ensure .jac/client directory exists
|
|
223
|
+
client_dir = bundler._get_client_dir();
|
|
224
|
+
client_dir.mkdir(parents=True, exist_ok=True);
|
|
225
|
+
|
|
226
|
+
# Copy package.json to .jac/client/ for npm install
|
|
227
|
+
configs_package_json = client_dir / 'configs' / 'package.json';
|
|
228
|
+
build_package_json = client_dir / 'package.json';
|
|
229
|
+
|
|
230
|
+
if not configs_package_json.exists() {
|
|
231
|
+
print(
|
|
232
|
+
"Warning: package.json was not generated, skipping package installation",
|
|
233
|
+
file=sys.stderr
|
|
234
|
+
);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
shutil.copy2(configs_package_json, build_package_json);
|
|
239
|
+
|
|
240
|
+
# Run npm install using NodeInstaller to handle NVM environment
|
|
241
|
+
try {
|
|
242
|
+
console.print(" Installing npm packages...", style="cyan");
|
|
243
|
+
with console.status(
|
|
244
|
+
"[cyan]Installing npm packages...[/cyan]", spinner="dots"
|
|
245
|
+
) as status {
|
|
246
|
+
NodeInstaller.run_npm_with_nvm(
|
|
247
|
+
['install'], cwd=client_dir, timeout=300
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# Move package-lock.json to configs/ if it was created
|
|
252
|
+
build_package_lock = client_dir / 'package-lock.json';
|
|
253
|
+
configs_package_lock = client_dir / 'configs' / 'package-lock.json';
|
|
254
|
+
if build_package_lock.exists() {
|
|
255
|
+
if configs_package_lock.exists() {
|
|
256
|
+
configs_package_lock.unlink();
|
|
257
|
+
}
|
|
258
|
+
shutil.move(str(build_package_lock), str(configs_package_lock));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
console.print(" ✔ npm packages installed", style="success");
|
|
262
|
+
} except subprocess.CalledProcessError as e {
|
|
263
|
+
console.warning(f"Failed to install packages: {e.stderr}");
|
|
264
|
+
console.print(
|
|
265
|
+
" 💡 You can install packages later with: jac add --cl\n",
|
|
266
|
+
style="muted"
|
|
267
|
+
);
|
|
268
|
+
} except FileNotFoundError {
|
|
269
|
+
console.warning(
|
|
270
|
+
"npm command not found. This may require reloading your shell."
|
|
271
|
+
);
|
|
272
|
+
console.print(
|
|
273
|
+
" 💡 You can install packages later with: jac add --cl\n",
|
|
274
|
+
style="muted"
|
|
275
|
+
);
|
|
276
|
+
} finally {
|
|
277
|
+
# Clean up temporary package.json
|
|
278
|
+
if build_package_json.exists() {
|
|
279
|
+
build_package_json.unlink();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} except Exception as e {
|
|
283
|
+
console.warning(f"Could not install default packages: {e}");
|
|
284
|
+
console.print(
|
|
285
|
+
" 💡 You can install packages later with: jac add --cl\n", style="muted"
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# If Node.js was just installed, offer to restart terminal for full integration
|
|
290
|
+
if was_just_installed {
|
|
291
|
+
console.print();
|
|
292
|
+
console.info("Node.js was just installed via NVM");
|
|
293
|
+
console.print(
|
|
294
|
+
"\n For Node.js to be available in your current shell:", style="muted"
|
|
295
|
+
);
|
|
296
|
+
console.print(" 1. Run: source ~/.nvm/nvm.sh", style="muted");
|
|
297
|
+
console.print(" 2. Or restart your terminal", style="muted");
|
|
298
|
+
console.print("\n 💡 npm commands from jac work immediately", style="info");
|
|
299
|
+
console.print();
|
|
300
|
+
}
|
|
89
301
|
}
|
|
90
302
|
|
|
303
|
+
# ===============================================================================
|
|
304
|
+
# NPM Dependency Handlers
|
|
305
|
+
# ===============================================================================
|
|
91
306
|
"""Install an npm package."""
|
|
92
307
|
def _npm_install_handler(
|
|
93
308
|
config: JacConfig,
|
|
@@ -101,15 +316,13 @@ def _npm_install_handler(
|
|
|
101
316
|
raise RuntimeError("No project root found") ;
|
|
102
317
|
}
|
|
103
318
|
|
|
104
|
-
project_dir = config.project_root;
|
|
105
|
-
|
|
106
319
|
# Add to config (core JacConfig handles this)
|
|
107
|
-
package_version = version or "latest";
|
|
320
|
+
package_version: str = version or "latest";
|
|
108
321
|
config.add_dependency(package_name, package_version, dev=is_dev, dep_type="npm");
|
|
109
322
|
config.save();
|
|
110
323
|
|
|
111
324
|
# Generate package.json and run npm install
|
|
112
|
-
_regenerate_and_install(
|
|
325
|
+
_regenerate_and_install(Path(str(config.project_root)));
|
|
113
326
|
}
|
|
114
327
|
|
|
115
328
|
"""Install all npm packages from jac.toml."""
|
|
@@ -118,7 +331,7 @@ def _npm_install_all_handler(config: JacConfig) -> None {
|
|
|
118
331
|
raise RuntimeError("No project root found") ;
|
|
119
332
|
}
|
|
120
333
|
|
|
121
|
-
_regenerate_and_install(config.project_root);
|
|
334
|
+
_regenerate_and_install(Path(str(config.project_root)));
|
|
122
335
|
}
|
|
123
336
|
|
|
124
337
|
"""Remove an npm package."""
|
|
@@ -129,7 +342,7 @@ def _npm_remove_handler(
|
|
|
129
342
|
raise RuntimeError("No project root found") ;
|
|
130
343
|
}
|
|
131
344
|
|
|
132
|
-
project_dir = config.project_root;
|
|
345
|
+
project_dir: Path = Path(str(config.project_root));
|
|
133
346
|
|
|
134
347
|
# Remove from config (core JacConfig handles this)
|
|
135
348
|
result = config.remove_dependency(package_name, dev=is_dev, dep_type="npm");
|
|
@@ -147,8 +360,28 @@ def _npm_remove_handler(
|
|
|
147
360
|
"""Regenerate package.json from jac.toml and run npm install."""
|
|
148
361
|
def _regenerate_and_install(project_dir: Path) -> None {
|
|
149
362
|
import from jac_client.plugin.src.vite_bundler { ViteBundler }
|
|
363
|
+
import from jac_client.plugin.utils.node_installer { NodeInstaller }
|
|
150
364
|
import shutil;
|
|
151
365
|
|
|
366
|
+
# Ensure Node.js is installed before proceeding
|
|
367
|
+
node_result = NodeInstaller.ensure_node_installed(interactive=True);
|
|
368
|
+
node_installed = node_result[0];
|
|
369
|
+
message = node_result[1];
|
|
370
|
+
was_just_installed = node_result[2];
|
|
371
|
+
|
|
372
|
+
if not node_installed {
|
|
373
|
+
raise RuntimeError(
|
|
374
|
+
f"Node.js installation required:\n{message}\n" + "Please install Node.js and try again."
|
|
375
|
+
) ;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
# Inform user if Node.js was just installed
|
|
379
|
+
if was_just_installed {
|
|
380
|
+
print(
|
|
381
|
+
"\nNote: Node.js was just installed. npm commands will work via NVM sourcing."
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
152
385
|
bundler = ViteBundler(project_dir);
|
|
153
386
|
bundler.create_package_json();
|
|
154
387
|
|
|
@@ -164,18 +397,13 @@ def _regenerate_and_install(project_dir: Path) -> None {
|
|
|
164
397
|
}
|
|
165
398
|
|
|
166
399
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
cwd=client_dir,
|
|
170
|
-
check=True,
|
|
171
|
-
capture_output=True,
|
|
172
|
-
text=True
|
|
173
|
-
);
|
|
400
|
+
# Use NodeInstaller.run_npm_with_nvm to handle NVM environment
|
|
401
|
+
NodeInstaller.run_npm_with_nvm(["install"], cwd=client_dir, timeout=300);
|
|
174
402
|
} except subprocess.CalledProcessError as e {
|
|
175
403
|
raise RuntimeError(f"Failed to install npm packages: {e.stderr}") from e ;
|
|
176
404
|
} except FileNotFoundError {
|
|
177
405
|
raise RuntimeError(
|
|
178
|
-
"npm command not found.
|
|
406
|
+
"npm command not found. This may require reloading your shell after Node.js installation."
|
|
179
407
|
) from None ;
|
|
180
408
|
} finally {
|
|
181
409
|
# Clean up temporary package.json
|
|
@@ -21,6 +21,7 @@ class JacClientConfig(PluginConfigBase) {
|
|
|
21
21
|
def save(self: JacClientConfig) -> None;
|
|
22
22
|
def get_vite_config(self: JacClientConfig) -> dict[str, Any];
|
|
23
23
|
def get_ts_config(self: JacClientConfig) -> dict[str, Any];
|
|
24
|
+
def get_configs(self: JacClientConfig) -> dict[str, Any];
|
|
24
25
|
def get_package_config(self: JacClientConfig) -> dict[str, Any];
|
|
25
26
|
def add_dependency(
|
|
26
27
|
self: JacClientConfig, name: str, version: str, is_dev: bool = False
|
|
@@ -49,7 +49,7 @@ impl ViteCompiler.create_entry_file(self: ViteCompiler, module_path: Path) -> No
|
|
|
49
49
|
entry_file = self.compiled_dir / '_entry.js';
|
|
50
50
|
# Derive the app module filename from the entry point (e.g., main.jac -> main.js, app.jac -> app.js)
|
|
51
51
|
app_module_name = module_path.stem;
|
|
52
|
-
entry_content = f'import React from "react";\nimport {{ createRoot }} from "react-dom/client";\nimport {{ app as App }} from "./{app_module_name}.js";\n\nconst root = createRoot(document.getElementById("root"));\nroot.render(
|
|
52
|
+
entry_content = f'import React from "react";\nimport {{ createRoot }} from "react-dom/client";\nimport {{ app as App }} from "./{app_module_name}.js";\nimport {{ JacClientErrorBoundary, ErrorFallback }} from "@jac-client/utils";\n\nconst root = createRoot(document.getElementById("root"));\nroot.render(\n\tReact.createElement(\n\t\tJacClientErrorBoundary,{{ FallbackComponent: ErrorFallback }},\n\t\tReact.createElement(App, null)\n\t)\n);\n';
|
|
53
53
|
entry_file.write_text(entry_content, encoding='utf-8');
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -27,6 +27,7 @@ impl JacClientConfig.get_default_config(self: JacClientConfig) -> dict[str, Any]
|
|
|
27
27
|
'resolve': {}
|
|
28
28
|
},
|
|
29
29
|
'ts': {'compilerOptions': {}, 'include': [], 'exclude': []},
|
|
30
|
+
'configs': {}, # Generic config file generation: { "postcss": {...}, "tailwind": {...} }
|
|
30
31
|
'package': {
|
|
31
32
|
'name': '',
|
|
32
33
|
'version': '1.0.0',
|
|
@@ -61,6 +62,7 @@ impl JacClientConfig.load(self: JacClientConfig) -> dict[str, Any] {
|
|
|
61
62
|
user_config: dict[str, Any] = {
|
|
62
63
|
'vite': client_config.get('vite', {}),
|
|
63
64
|
'ts': client_config.get('ts', {}),
|
|
65
|
+
'configs': client_config.get('configs', {}),
|
|
64
66
|
'package': {
|
|
65
67
|
'name': jac_config.project.name,
|
|
66
68
|
'version': jac_config.project.version,
|
|
@@ -91,6 +93,12 @@ impl JacClientConfig.get_ts_config(self: JacClientConfig) -> dict[str, Any] {
|
|
|
91
93
|
return config.get('ts', {});
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
"""Get generic config files to generate (postcss, tailwind, etc.)."""
|
|
97
|
+
impl JacClientConfig.get_configs(self: JacClientConfig) -> dict[str, Any] {
|
|
98
|
+
config = self.load();
|
|
99
|
+
return config.get('configs', {});
|
|
100
|
+
}
|
|
101
|
+
|
|
94
102
|
"""Save configuration back to jac.toml using core JacConfig."""
|
|
95
103
|
impl JacClientConfig.save(self: JacClientConfig) -> None {
|
|
96
104
|
jac_config = self.get_jac_config();
|
|
@@ -78,6 +78,8 @@ impl ViteBundler.create_package_json(
|
|
|
78
78
|
}
|
|
79
79
|
# Generate tsconfig.json during build time
|
|
80
80
|
self.create_tsconfig();
|
|
81
|
+
# Generate config files (postcss, tailwind, etc.) from jac.toml
|
|
82
|
+
self.create_config_files();
|
|
81
83
|
return package_json_path;
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -375,6 +377,8 @@ impl ViteBundler.find_bundle(self: ViteBundler) -> Optional[Path] {
|
|
|
375
377
|
|
|
376
378
|
"""Run Vite build with generated config in .jac/client/configs/."""
|
|
377
379
|
impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) -> None {
|
|
380
|
+
import sys;
|
|
381
|
+
import time;
|
|
378
382
|
self.output_dir.mkdir(parents=True, exist_ok=True);
|
|
379
383
|
generated_package_json = self._get_client_dir() / 'configs' / 'package.json';
|
|
380
384
|
if not generated_package_json.exists() {
|
|
@@ -395,18 +399,31 @@ impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) ->
|
|
|
395
399
|
shutil.copy2(configs_package_json, build_package_json);
|
|
396
400
|
}
|
|
397
401
|
try {
|
|
398
|
-
# Install to .jac/client/node_modules
|
|
399
|
-
|
|
400
|
-
|
|
402
|
+
# Install to .jac/client/node_modules with progress feedback
|
|
403
|
+
print(
|
|
404
|
+
" ⏳ Installing npm dependencies (this may take a minute)...",
|
|
405
|
+
flush=True
|
|
406
|
+
);
|
|
407
|
+
start_time = time.time();
|
|
408
|
+
result = subprocess.run(
|
|
409
|
+
['npm', 'install', '--progress'],
|
|
401
410
|
cwd=build_dir,
|
|
402
|
-
check=
|
|
411
|
+
check=False,
|
|
403
412
|
capture_output=True,
|
|
404
413
|
text=True
|
|
405
414
|
);
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
415
|
+
elapsed = time.time() - start_time;
|
|
416
|
+
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
|
+
) ;
|
|
425
|
+
}
|
|
426
|
+
print(f" ✔ Dependencies installed ({elapsed:.1f}s)", flush=True);
|
|
410
427
|
} except FileNotFoundError {
|
|
411
428
|
raise None from ClientBundleError(
|
|
412
429
|
'npm command not found. Ensure Node.js and npm are installed.'
|
|
@@ -431,15 +448,20 @@ impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) ->
|
|
|
431
448
|
command = ['npm', 'run', 'build'];
|
|
432
449
|
}
|
|
433
450
|
# Run vite from client build directory so it can find node_modules
|
|
451
|
+
print(" ⏳ Building client bundle...", flush=True);
|
|
452
|
+
start_time = time.time();
|
|
434
453
|
result = subprocess.run(
|
|
435
454
|
command, cwd=build_dir, check=False, capture_output=True, text=True
|
|
436
455
|
);
|
|
456
|
+
elapsed = time.time() - start_time;
|
|
437
457
|
if result.returncode != 0 {
|
|
458
|
+
print(f"\n ✖ Vite build failed after {elapsed:.1f}s", file=sys.stderr);
|
|
438
459
|
error_msg = result.stderr or result.stdout or 'Unknown error';
|
|
439
460
|
raise ClientBundleError(
|
|
440
461
|
f"Vite build failed:\n{error_msg}\nCommand: {' '.join(command)}"
|
|
441
462
|
) from None ;
|
|
442
463
|
}
|
|
464
|
+
print(f" ✔ Client bundle built ({elapsed:.1f}s)", flush=True);
|
|
443
465
|
} finally {
|
|
444
466
|
# Clean up temporary package.json in client build dir
|
|
445
467
|
build_package_json = build_dir / 'package.json';
|
|
@@ -558,8 +580,54 @@ export default defineConfig({{
|
|
|
558
580
|
return config_path;
|
|
559
581
|
}
|
|
560
582
|
|
|
583
|
+
"""Create config files from jac.toml [plugins.client.configs].
|
|
584
|
+
|
|
585
|
+
Generates JavaScript config files (e.g., postcss.config.js, tailwind.config.js)
|
|
586
|
+
from TOML configuration. Each key in [plugins.client.configs] becomes a config file.
|
|
587
|
+
|
|
588
|
+
Example jac.toml:
|
|
589
|
+
[plugins.client.configs.postcss]
|
|
590
|
+
plugins = ["tailwindcss", "autoprefixer"]
|
|
591
|
+
|
|
592
|
+
[plugins.client.configs.tailwind]
|
|
593
|
+
content = ["./src/**/*.{js,jsx}"]
|
|
594
|
+
|
|
595
|
+
This generates:
|
|
596
|
+
- .jac/client/configs/postcss.config.js
|
|
597
|
+
- .jac/client/configs/tailwind.config.js
|
|
598
|
+
"""
|
|
599
|
+
impl ViteBundler.create_config_files(self: ViteBundler) -> list[Path] {
|
|
600
|
+
configs = self.config_loader.get_configs();
|
|
601
|
+
if not configs {
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
build_dir = self._get_client_dir();
|
|
605
|
+
configs_dir = build_dir / 'configs';
|
|
606
|
+
configs_dir.mkdir(parents=True, exist_ok=True);
|
|
607
|
+
created_files: list[Path] = [];
|
|
608
|
+
for (config_name, config_data) in configs.items() {
|
|
609
|
+
config_path = configs_dir / f'{config_name}.config.js';
|
|
610
|
+
|
|
611
|
+
# Convert the TOML config to JavaScript module.exports
|
|
612
|
+
js_content = _toml_config_to_js(config_name, config_data);
|
|
613
|
+
config_path.write_text(js_content, encoding='utf-8');
|
|
614
|
+
created_files.append(config_path);
|
|
615
|
+
}
|
|
616
|
+
return created_files;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
"""Convert TOML config data to JavaScript config file content.
|
|
620
|
+
|
|
621
|
+
Generates a generic module.exports with the config data as JSON.
|
|
622
|
+
"""
|
|
623
|
+
def _toml_config_to_js(config_name: str, config_data: dict) -> str {
|
|
624
|
+
return f"module.exports = {json.dumps(config_data, indent=2)};\n";
|
|
625
|
+
}
|
|
626
|
+
|
|
561
627
|
"""Start Vite dev server as a subprocess."""
|
|
562
628
|
impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
|
|
629
|
+
import sys;
|
|
630
|
+
import time;
|
|
563
631
|
build_dir = self._get_client_dir();
|
|
564
632
|
node_modules = build_dir / 'node_modules';
|
|
565
633
|
# Create/update index.html for dev server (load from compiled/ for HMR)
|
|
@@ -590,17 +658,30 @@ impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
|
|
|
590
658
|
shutil.copy2(generated_package_json, build_package_json);
|
|
591
659
|
}
|
|
592
660
|
try {
|
|
593
|
-
print(
|
|
594
|
-
|
|
595
|
-
|
|
661
|
+
print(
|
|
662
|
+
" ⏳ Installing npm dependencies (this may take a minute)...",
|
|
663
|
+
flush=True
|
|
664
|
+
);
|
|
665
|
+
start_time = time.time();
|
|
666
|
+
result = subprocess.run(
|
|
667
|
+
['npm', 'install', '--progress'],
|
|
596
668
|
cwd=build_dir,
|
|
597
|
-
check=
|
|
669
|
+
check=False,
|
|
598
670
|
capture_output=True,
|
|
599
671
|
text=True
|
|
600
672
|
);
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
673
|
+
elapsed = time.time() - start_time;
|
|
674
|
+
if result.returncode != 0 {
|
|
675
|
+
print(
|
|
676
|
+
f"\n ✖ npm install failed after {elapsed:.1f}s", file=sys.stderr
|
|
677
|
+
);
|
|
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
|
+
) ;
|
|
683
|
+
}
|
|
684
|
+
print(f" ✔ Dependencies installed ({elapsed:.1f}s)", flush=True);
|
|
604
685
|
} finally {
|
|
605
686
|
# Clean up temp package.json
|
|
606
687
|
if build_package_json.exists() {
|
|
@@ -616,7 +697,7 @@ impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
|
|
|
616
697
|
) ;
|
|
617
698
|
}
|
|
618
699
|
config_rel = dev_config.relative_to(build_dir);
|
|
619
|
-
|
|
700
|
+
logger.debug(f"Starting Vite dev server on port {port}");
|
|
620
701
|
# Start Vite in dev mode (let output go to terminal for HMR visibility)
|
|
621
702
|
process = subprocess.Popen(
|
|
622
703
|
['npx', 'vite', '--config', str(config_rel), '--port', str(port)],
|
|
@@ -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
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(
|
|
@@ -34,6 +37,9 @@ class ViteBundler {
|
|
|
34
37
|
) -> Path;
|
|
35
38
|
|
|
36
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
|
+
|
|
37
43
|
"""Create a dev-mode vite config with API proxy for HMR."""
|
|
38
44
|
def create_dev_vite_config(
|
|
39
45
|
self: ViteBundler, entry_file: Path, api_port: int = 8000
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utility modules for jac-client plugin."""
|