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.
Files changed (52) hide show
  1. jac_client/examples/all-in-one/{app.jac → main.jac} +5 -5
  2. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +8 -1
  3. jac_client/examples/all-in-one/pages/FeaturesTest.jac +16 -1
  4. jac_client/examples/all-in-one/pages/{FeaturesTest.cl.jac → features_test_ui.cl.jac} +11 -0
  5. jac_client/examples/all-in-one/pages/nestedDemo.jac +1 -1
  6. jac_client/examples/all-in-one/pages/notFound.jac +2 -7
  7. jac_client/plugin/cli.jac +162 -435
  8. jac_client/plugin/client.jac +25 -0
  9. jac_client/plugin/client_runtime.cl.jac +5 -1
  10. jac_client/plugin/impl/client.impl.jac +96 -55
  11. jac_client/plugin/impl/client_runtime.impl.jac +154 -0
  12. jac_client/plugin/plugin_config.jac +243 -15
  13. jac_client/plugin/src/config_loader.jac +1 -0
  14. jac_client/plugin/src/impl/compiler.impl.jac +1 -1
  15. jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
  16. jac_client/plugin/src/impl/vite_bundler.impl.jac +97 -16
  17. jac_client/plugin/src/vite_bundler.jac +6 -0
  18. jac_client/plugin/utils/__init__.jac +1 -0
  19. jac_client/plugin/utils/impl/node_installer.impl.jac +249 -0
  20. jac_client/plugin/utils/node_installer.jac +41 -0
  21. jac_client/templates/client.jacpack +72 -0
  22. jac_client/templates/fullstack.jacpack +61 -0
  23. jac_client/tests/conftest.py +48 -7
  24. jac_client/tests/test_cli.py +184 -70
  25. jac_client/tests/test_e2e.py +232 -0
  26. jac_client/tests/test_helpers.py +65 -0
  27. jac_client/tests/test_it.py +91 -135
  28. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/METADATA +4 -4
  29. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/RECORD +52 -45
  30. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/WHEEL +1 -1
  31. /jac_client/examples/all-in-one/pages/{BudgetPlanner.cl.jac → budget_planner_ui.cl.jac} +0 -0
  32. /jac_client/examples/asset-serving/css-with-image/{src/app.jac → main.jac} +0 -0
  33. /jac_client/examples/asset-serving/image-asset/{src/app.jac → main.jac} +0 -0
  34. /jac_client/examples/asset-serving/import-alias/{src/app.jac → main.jac} +0 -0
  35. /jac_client/examples/basic/{src/app.jac → main.jac} +0 -0
  36. /jac_client/examples/basic-auth/{src/app.jac → main.jac} +0 -0
  37. /jac_client/examples/basic-auth-with-router/{src/app.jac → main.jac} +0 -0
  38. /jac_client/examples/basic-full-stack/{src/app.jac → main.jac} +0 -0
  39. /jac_client/examples/css-styling/js-styling/{src/app.jac → main.jac} +0 -0
  40. /jac_client/examples/css-styling/material-ui/{src/app.jac → main.jac} +0 -0
  41. /jac_client/examples/css-styling/pure-css/{src/app.jac → main.jac} +0 -0
  42. /jac_client/examples/css-styling/sass-example/{src/app.jac → main.jac} +0 -0
  43. /jac_client/examples/css-styling/styled-components/{src/app.jac → main.jac} +0 -0
  44. /jac_client/examples/css-styling/tailwind-example/{src/app.jac → main.jac} +0 -0
  45. /jac_client/examples/full-stack-with-auth/{src/app.jac → main.jac} +0 -0
  46. /jac_client/examples/little-x/{src/app.jac → main.jac} +0 -0
  47. /jac_client/examples/nested-folders/nested-advance/{src/app.jac → main.jac} +0 -0
  48. /jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +0 -0
  49. /jac_client/examples/ts-support/{src/app.jac → main.jac} +0 -0
  50. /jac_client/examples/with-router/{src/app.jac → main.jac} +0 -0
  51. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/entry_points.txt +0 -0
  52. {jac_client-0.2.8.dist-info → jac_client-0.2.9.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,31 @@ glob JsonValue: TypeAlias = None | str | int | float | bool | list['JsonValue']
18
18
  ],
19
19
  StatusCode: TypeAlias = Literal[(200, 201, 400, 401, 404, 503)];
20
20
 
21
+ """Builds HTML head content from meta data."""
22
+ obj HeaderBuilder {
23
+ has meta_data: dict,
24
+ function_name: str,
25
+ DEFAULTS: dict = {
26
+ "charset": "UTF-8",
27
+ "viewport": "width=device-width,initial-scale=1"
28
+ },
29
+ standard_tags: list = [
30
+ "description",
31
+ "keywords",
32
+ "author",
33
+ "robots",
34
+ "theme_color"
35
+ ];
36
+
37
+ def build_head -> str;
38
+ def _get_value(key: str) -> str;
39
+ def _build_special_tags -> list[str];
40
+ def _build_meta_tags -> list[str];
41
+ def _build_og_tags -> list[str];
42
+ def _build_link_tags -> list[str];
43
+ def _build_generic_meta_tags -> list[str];
44
+ }
45
+
21
46
  """Jac Client."""
22
47
  class JacClient {
23
48
  @hookimpl
@@ -13,6 +13,7 @@ import from 'react-router-dom' {
13
13
  useLocation as reactRouterUseLocation,
14
14
  useParams as reactRouterUseParams
15
15
  }
16
+ import from 'react-error-boundary' { ErrorBoundary }
16
17
 
17
18
  def:pub __jacJsx(tag: any, props: dict = {}, children: any = []) -> any;
18
19
 
@@ -25,7 +26,8 @@ glob:pub useState = reactUseState,
25
26
  Navigate = ReactRouterNavigate,
26
27
  useNavigate = reactRouterUseNavigate,
27
28
  useLocation = reactRouterUseLocation,
28
- useParams = reactRouterUseParams;
29
+ useParams = reactRouterUseParams,
30
+ JacClientErrorBoundary = ErrorBoundary;
29
31
 
30
32
  def:pub useRouter -> dict;
31
33
  def:pub navigate(path: str) -> None;
@@ -39,4 +41,6 @@ def:pub jacIsLoggedIn -> bool;
39
41
  def:pub __getLocalStorage(key: str) -> str;
40
42
  def:pub __setLocalStorage(key: str, value: str) -> None;
41
43
  def:pub __removeLocalStorage(key: str) -> None;
44
+ def:pub ErrorFallback(error: str, resetErrorBoundary: any) -> any;
45
+ def:pub errorOverlay(filePath: str, errors: str) -> any;
42
46
  # React Router components
@@ -126,61 +126,8 @@ impl JacClient.render_page(
126
126
  # Get meta data from config
127
127
  client_cfg = config.get_plugin_config("client") if config else None;
128
128
  meta_data = client_cfg.get("app_meta_data", {}) if client_cfg else {};
129
- charset = meta_data.get("charset", "UTF-8");
130
- title = meta_data.get("title", function_name);
131
- viewport = meta_data.get("viewport", "width=device-width, initial-scale=1");
132
- description = meta_data.get("description", None);
133
- robots = meta_data.get("robots", "index, follow");
134
- canonical = meta_data.get("canonical", None);
135
- og_type = meta_data.get("og_type", "website");
136
- og_title = meta_data.get("og_title", title);
137
- og_description = meta_data.get("og_description", None);
138
- og_url = meta_data.get("og_url", None);
139
- og_image = meta_data.get("og_image", None);
140
- theme_color = meta_data.get("theme_color", "#ffffff");
141
- icon = meta_data.get("icon", None);
142
- # Build head content from TOML metadata
143
- head_content = f'<meta charset="{html.escape(charset)}"/>\n <meta name="viewport" content="{html.escape(
144
- viewport
145
- )}"/>\n <title>{html.escape(title)}</title>';
146
- head_content += f'\n <meta name="robots" content="{html.escape(robots)}"/>';
147
- head_content += f'\n <meta name="theme-color" content="{html.escape(
148
- theme_color
149
- )}"/>';
150
- head_content += f'\n <meta property="og:type" content="{html.escape(
151
- og_type
152
- )}"/>';
153
- head_content += f'\n <meta property="og:title" content="{html.escape(
154
- og_title
155
- )}"/>';
156
- if description {
157
- head_content += f'\n <meta name="description" content="{html.escape(
158
- description
159
- )}"/>';
160
- }
161
- if canonical {
162
- head_content += f'\n <link rel="canonical" href="{html.escape(
163
- canonical
164
- )}"/>';
165
- }
166
- if icon {
167
- head_content += f'\n <link rel="icon" href="{html.escape(icon)}"/>';
168
- }
169
- if og_url {
170
- head_content += f'\n <meta property="og:url" content="{html.escape(
171
- og_url
172
- )}"/>';
173
- }
174
- if og_image {
175
- head_content += f'\n <meta property="og:image" content="{html.escape(
176
- og_image
177
- )}"/>';
178
- }
179
- if og_description {
180
- head_content += f'\n <meta property="og:description" content="{html.escape(
181
- og_description
182
- )}"/>';
183
- }
129
+ head_builder = HeaderBuilder(meta_data, function_name);
130
+ head_content = head_builder.build_head();
184
131
  if css_link {
185
132
  head_content += f"\n {css_link}";
186
133
  }
@@ -191,3 +138,97 @@ impl JacClient.render_page(
191
138
  'bundle_code': introspector._bundle.code
192
139
  };
193
140
  }
141
+
142
+ """Build complete HTML head."""
143
+ impl HeaderBuilder.build_head -> str {
144
+ lines: list = [];
145
+ lines.extend(self._build_special_tags()); # charset, viewport, title
146
+ lines.extend(self._build_meta_tags()); # description, robots, etc
147
+ lines.extend(self._build_og_tags()); # Open Graph
148
+ lines.extend(self._build_link_tags()); # canonical, icon, etc
149
+ lines.extend(self._build_generic_meta_tags()); # custom meta_* tags
150
+ return "\n ".join(lines);
151
+ }
152
+
153
+ """Build generic meta tags (meta_* prefix for any custom metadata)."""
154
+ impl HeaderBuilder._build_generic_meta_tags -> list[str] {
155
+ lines: list = [];
156
+ for (key, val) in self.meta_data.items() {
157
+ if key.startswith("meta_") and val {
158
+ # meta_custom_field → name="custom-field"
159
+ name = key[5:].replace("_", "-");
160
+ lines.append(f'<meta name={name} content={html.escape(str(val))} />');
161
+ }
162
+ }
163
+ return lines;
164
+ }
165
+
166
+ """Build link tags (link_* prefix or special cases)."""
167
+ impl HeaderBuilder._build_link_tags -> list[str] {
168
+ lines: list = [];
169
+ special_links = {
170
+ "canonical": "canonical",
171
+ "icon": "icon",
172
+ "apple_touch_icon": "apple-touch-icon",
173
+ "manifest": "manifest"
174
+ };
175
+ for (key, rel) in special_links.items() {
176
+ if val := self._get_value(key) {
177
+ lines.append(f'<link rel={rel} href={html.escape(str(val))} />');
178
+ }
179
+ }
180
+ for (key, val) in self.meta_data.items() {
181
+ if key.startswith("link_") and val {
182
+ # link_preconnect_google → rel="preconnect"
183
+ rel = key[5:].replace("_", "-");
184
+ lines.append(f'<link rel={rel} href={html.escape(str(val))} />');
185
+ }
186
+ }
187
+ return lines;
188
+ }
189
+
190
+ """Build Open Graph tags (og_* prefix)."""
191
+ impl HeaderBuilder._build_og_tags -> list[str] {
192
+ lines: list = [];
193
+ for (key, val) in self.meta_data.items() {
194
+ if key.startswith("og_") and val {
195
+ property_name = key[3:].replace("_", ":");
196
+ lines.append(
197
+ f'<meta property=og:{property_name} content={html.escape(str(val))} />'
198
+ );
199
+ }
200
+ }
201
+ return lines;
202
+ }
203
+
204
+ """Build standard meta tags (description, robots, keywords, author, etc)."""
205
+ impl HeaderBuilder._build_meta_tags -> list[str] {
206
+ lines: list = [];
207
+ for key in self.standard_tags {
208
+ if val := self._get_value(key) {
209
+ # Convert underscore to dash (theme_color → theme-color)
210
+ name = key.replace("_", "-");
211
+ lines.append(f'<meta name={name} content={html.escape(val)} />');
212
+ }
213
+ }
214
+ return lines;
215
+ }
216
+
217
+ """Build special tags (charset, viewport, title)."""
218
+ impl HeaderBuilder._build_special_tags -> list[str] {
219
+ lines: list = [];
220
+ if charset := self._get_value("charset") {
221
+ lines.append(f'<meta charset={html.escape(charset)} />');
222
+ }
223
+ if viewport := self._get_value("viewport") {
224
+ lines.append(f'<meta name="viewport" content={html.escape(viewport)} />');
225
+ }
226
+ title = self._get_value("title") or self.function_name;
227
+ lines.append(f'<title>{html.escape(title)}</title>');
228
+ return lines;
229
+ }
230
+
231
+ """Get value with fallback to defaults."""
232
+ impl HeaderBuilder._get_value(key: str) -> str {
233
+ return self.meta_data.get(key) or self.DEFAULTS.get(key);
234
+ }
@@ -193,3 +193,157 @@ impl __removeLocalStorage(key: str) -> None {
193
193
  storage.removeItem(key);
194
194
  }
195
195
  }
196
+
197
+ impl ErrorFallback(error: str, resetErrorBoundary: any) -> any {
198
+ return
199
+ <div
200
+ role="alert"
201
+ style={{
202
+ minHeight: "100vh",
203
+ display: "flex",
204
+ justifyContent: "center",
205
+ alignItems: "center",
206
+ backgroundColor: "#f9fafb",
207
+ fontFamily: "system-ui, sans-serif",
208
+
209
+ }}
210
+ >
211
+ <div
212
+ role="alert"
213
+ style={{
214
+ minHeight: "100vh",
215
+ display: "flex",
216
+ flexDirection: "column",
217
+ justifyContent: "center",
218
+ alignItems: "center",
219
+ backgroundColor: "#f9fafb",
220
+ fontFamily: "system-ui, sans-serif",
221
+
222
+ }}
223
+ >
224
+ <h2 style={{color: "#dc2626", marginBottom: "12px"}}>
225
+ 🚨 Something went wrong
226
+ </h2>
227
+ <p style={{color: "#374151", marginBottom: "16px"}}>
228
+ An unexpected error occurred. Please try again.
229
+ </p>
230
+ <pre
231
+ style={{
232
+ color: "#991b1b",
233
+ background: "#fee2e2",
234
+ padding: "12px",
235
+ borderRadius: "8px",
236
+ fontSize: "14px",
237
+ overflowX: "auto",
238
+ marginBottom: "16px",
239
+
240
+ }}
241
+ >
242
+ {error.error.message}
243
+ </pre>
244
+ <button
245
+ onClick={lambda -> None { error.resetErrorBoundary();}}
246
+ style={{
247
+ backgroundColor: "#2563eb",
248
+ color: "#fff",
249
+ padding: "10px 16px",
250
+ borderRadius: "8px",
251
+ border: "none",
252
+ cursor: "pointer",
253
+ fontSize: "14px",
254
+
255
+ }}
256
+ >
257
+ 🔄 Try again
258
+ </button>
259
+ </div>
260
+ </div>;
261
+ }
262
+
263
+ impl errorOverlay(filePath: str, errors: str) -> any {
264
+ return (
265
+ <div
266
+ style={{
267
+ position: "fixed",
268
+ top: 0,
269
+ left: 0,
270
+ width: "100%",
271
+ height: "100%",
272
+ background: "rgba(0, 0, 0, 0.85)",
273
+ color: "#fff",
274
+ fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
275
+ fontSize: 14,
276
+ zIndex: 999999,
277
+ overflow: "auto",
278
+ padding: 20,
279
+ boxSizing: "border-box",
280
+
281
+ }}
282
+ >
283
+ <div style={{maxWidth: 1200, margin: "0 auto"}}>
284
+ <div
285
+ style={{
286
+ background: "#d32f2f",
287
+ color: "white",
288
+ padding: "16px 24px",
289
+ borderRadius: "8px 8px 0 0",
290
+ fontSize: 18,
291
+ fontWeight: "bold",
292
+
293
+ }}
294
+ >
295
+ ⚠️ Compilation Error
296
+ </div>
297
+ <div
298
+ style={{
299
+ background: "#1e1e1e",
300
+ padding: 24,
301
+ borderRadius: "0 0 8px 8px",
302
+
303
+ }}
304
+ >
305
+ <div style={{marginBottom: 16}}>
306
+ <div style={{color: "#888", marginBottom: 8}}>
307
+ File:
308
+ </div>
309
+ <div style={{color: "#64b5f6", fontWeight: "bold"}}>
310
+ {filePath}
311
+ </div>
312
+ </div>
313
+ <div>
314
+ <div style={{color: "#888", marginBottom: 8}}>
315
+ Error:
316
+ </div>
317
+ <pre
318
+ style={{
319
+ background: "#2d2d2d",
320
+ padding: 16,
321
+ borderRadius: 4,
322
+ overflowX: "auto",
323
+ margin: 0,
324
+ borderLeft: "4px solid #d32f2f",
325
+ lineHeight: 1.6,
326
+ color: "#ff6b6b",
327
+
328
+ }}
329
+ >
330
+ {errors}
331
+ </pre>
332
+ </div>
333
+ <div
334
+ style={{
335
+ marginTop: 24,
336
+ paddingTop: 16,
337
+ borderTop: "1px solid #444",
338
+ color: "#888",
339
+ fontSize: 13,
340
+
341
+ }}
342
+ >
343
+ 💡 Fix the error and save the file to continue development.
344
+ </div>
345
+ </div>
346
+ </div>
347
+ </div>
348
+ );
349
+ }