jac-client 0.1.0__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/docs/README.md +629 -0
- jac_client/docs/advanced-state.md +706 -0
- jac_client/docs/imports.md +650 -0
- jac_client/docs/lifecycle-hooks.md +554 -0
- jac_client/docs/routing.md +530 -0
- jac_client/examples/little-x/app.jac +615 -0
- jac_client/examples/little-x/package-lock.json +2840 -0
- jac_client/examples/little-x/package.json +23 -0
- jac_client/examples/little-x/submit-button.jac +8 -0
- jac_client/examples/todo-app/README.md +82 -0
- jac_client/examples/todo-app/app.jac +683 -0
- jac_client/examples/todo-app/package-lock.json +999 -0
- jac_client/examples/todo-app/package.json +22 -0
- jac_client/plugin/cli.py +328 -0
- jac_client/plugin/client.py +41 -0
- jac_client/plugin/client_runtime.jac +941 -0
- jac_client/plugin/vite_client_bundle.py +470 -0
- jac_client/tests/__init__.py +2 -0
- jac_client/tests/fixtures/button.jac +6 -0
- jac_client/tests/fixtures/client_app.jac +18 -0
- jac_client/tests/fixtures/client_app_with_antd.jac +21 -0
- jac_client/tests/fixtures/js_import.jac +30 -0
- jac_client/tests/fixtures/package-lock.json +329 -0
- jac_client/tests/fixtures/package.json +11 -0
- jac_client/tests/fixtures/relative_import.jac +13 -0
- jac_client/tests/fixtures/test_fragments_spread.jac +44 -0
- jac_client/tests/fixtures/utils.js +22 -0
- jac_client/tests/test_cl.py +360 -0
- jac_client/tests/test_create_jac_app.py +139 -0
- jac_client-0.1.0.dist-info/METADATA +126 -0
- jac_client-0.1.0.dist-info/RECORD +33 -0
- jac_client-0.1.0.dist-info/WHEEL +4 -0
- jac_client-0.1.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""Tests for Vite client bundle generation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import tempfile
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
from jaclang.runtimelib.machine import JacMachine as Jac
|
|
10
|
+
from jaclang.utils.test import TestCase
|
|
11
|
+
from jac_client.plugin.vite_client_bundle import ViteClientBundleBuilder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ViteClientBundleBuilderTests(TestCase):
|
|
15
|
+
"""Validate Vite-powered client bundle compilation."""
|
|
16
|
+
|
|
17
|
+
def setUp(self) -> None:
|
|
18
|
+
Jac.reset_machine()
|
|
19
|
+
return super().setUp()
|
|
20
|
+
|
|
21
|
+
def tearDown(self) -> None:
|
|
22
|
+
Jac.reset_machine()
|
|
23
|
+
return super().tearDown()
|
|
24
|
+
|
|
25
|
+
def _create_test_project_with_vite(self, temp_path: Path, include_antd: bool = False) -> tuple[Path, Path]:
|
|
26
|
+
"""Create a minimal test project with Vite installed.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
temp_path: Path to the temporary directory
|
|
30
|
+
include_antd: If True, includes antd in dependencies
|
|
31
|
+
"""
|
|
32
|
+
# Create package.json with base dependencies
|
|
33
|
+
dependencies = {
|
|
34
|
+
"react": "^18.0.0",
|
|
35
|
+
"react-dom": "^18.0.0"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Add antd if requested
|
|
39
|
+
if include_antd:
|
|
40
|
+
dependencies["antd"] = "^5.0.0"
|
|
41
|
+
|
|
42
|
+
# Format dependencies for JSON
|
|
43
|
+
deps_str = ",\n".join(f' "{k}": "{v}"' for k, v in dependencies.items())
|
|
44
|
+
|
|
45
|
+
package_json = temp_path / "package.json"
|
|
46
|
+
package_json.write_text(f"""{{
|
|
47
|
+
"name": "test-client",
|
|
48
|
+
"version": "0.0.1",
|
|
49
|
+
"dependencies": {{
|
|
50
|
+
{deps_str}
|
|
51
|
+
}},
|
|
52
|
+
"devDependencies": {{
|
|
53
|
+
"vite": "^5.0.0"
|
|
54
|
+
}}
|
|
55
|
+
}}""", encoding="utf-8")
|
|
56
|
+
|
|
57
|
+
# Install dependencies
|
|
58
|
+
subprocess.run(
|
|
59
|
+
["npm", "install"],
|
|
60
|
+
cwd=temp_path,
|
|
61
|
+
check=True,
|
|
62
|
+
capture_output=True,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Create output directory
|
|
66
|
+
output_dir = temp_path / "static" / "client" / "js"
|
|
67
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
temp_dir = temp_path / "temp"
|
|
70
|
+
temp_dir.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
return package_json, output_dir
|
|
73
|
+
|
|
74
|
+
def test_build_bundle_with_vite(self) -> None:
|
|
75
|
+
"""Test that Vite bundling produces optimized output with proper structure."""
|
|
76
|
+
# Create a temporary directory for our test project
|
|
77
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
78
|
+
temp_path = Path(temp_dir)
|
|
79
|
+
|
|
80
|
+
# Create project with Vite installed
|
|
81
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path)
|
|
82
|
+
|
|
83
|
+
# Initialize the Vite builder
|
|
84
|
+
builder = ViteClientBundleBuilder(
|
|
85
|
+
vite_package_json=package_json,
|
|
86
|
+
vite_output_dir=output_dir,
|
|
87
|
+
vite_minify=False, # Disable minification for easier inspection
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Import the test module
|
|
91
|
+
fixtures_dir = Path(__file__).parent / "fixtures"
|
|
92
|
+
(module,) = Jac.jac_import("client_app", str(fixtures_dir))
|
|
93
|
+
|
|
94
|
+
# Build the bundle
|
|
95
|
+
bundle = builder.build(module, force=True)
|
|
96
|
+
|
|
97
|
+
# Verify bundle structure
|
|
98
|
+
self.assertIsNotNone(bundle)
|
|
99
|
+
self.assertEqual(bundle.module_name, "client_app")
|
|
100
|
+
self.assertIn("client_page", bundle.client_functions)
|
|
101
|
+
self.assertIn("ButtonProps", bundle.client_functions)
|
|
102
|
+
self.assertIn("API_LABEL", bundle.client_globals)
|
|
103
|
+
self.assertGreater(len(bundle.hash), 10)
|
|
104
|
+
|
|
105
|
+
# Verify bundle code contains expected content
|
|
106
|
+
self.assertIn("function client_page()", bundle.code)
|
|
107
|
+
self.assertIn('const API_LABEL = "Runtime Test";', bundle.code)
|
|
108
|
+
|
|
109
|
+
# Verify the Jac initialization is present
|
|
110
|
+
self.assertIn("__jacRegisterClientModule", bundle.code)
|
|
111
|
+
self.assertIn("globalThis.start_app", bundle.code)
|
|
112
|
+
|
|
113
|
+
# Verify bundle was written to output directory
|
|
114
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
115
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
116
|
+
|
|
117
|
+
# Verify cached bundle is identical
|
|
118
|
+
cached = builder.build(module, force=False)
|
|
119
|
+
self.assertEqual(bundle.hash, cached.hash)
|
|
120
|
+
self.assertEqual(bundle.code, cached.code)
|
|
121
|
+
|
|
122
|
+
def test_vite_bundle_without_package_json(self) -> None:
|
|
123
|
+
"""Test that missing package.json raises appropriate error."""
|
|
124
|
+
fixtures_dir = Path(__file__).parent / "fixtures"
|
|
125
|
+
(module,) = Jac.jac_import("client_app", str(fixtures_dir))
|
|
126
|
+
|
|
127
|
+
# Create builder without package.json
|
|
128
|
+
builder = ViteClientBundleBuilder(
|
|
129
|
+
vite_package_json=Path("/nonexistent/package.json"),
|
|
130
|
+
vite_output_dir=Path("/tmp/output"),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Building should raise an error
|
|
134
|
+
from jaclang.runtimelib.client_bundle import ClientBundleError
|
|
135
|
+
with self.assertRaises(ClientBundleError) as cm:
|
|
136
|
+
builder.build(module, force=True)
|
|
137
|
+
|
|
138
|
+
self.assertIn("Vite package.json not found", str(cm.exception))
|
|
139
|
+
|
|
140
|
+
def test_global_exposure_in_bundle(self) -> None:
|
|
141
|
+
"""Test that client functions are properly exposed globally for Vite IIFE."""
|
|
142
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
143
|
+
temp_path = Path(temp_dir)
|
|
144
|
+
|
|
145
|
+
# Create project with Vite installed
|
|
146
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path)
|
|
147
|
+
|
|
148
|
+
# Initialize the Vite builder
|
|
149
|
+
builder = ViteClientBundleBuilder(
|
|
150
|
+
vite_package_json=package_json,
|
|
151
|
+
vite_output_dir=output_dir,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Import the test module
|
|
155
|
+
fixtures_dir = Path(__file__).parent / "fixtures"
|
|
156
|
+
(module,) = Jac.jac_import("client_app", str(fixtures_dir))
|
|
157
|
+
|
|
158
|
+
# Build the bundle
|
|
159
|
+
bundle = builder.build(module, force=True)
|
|
160
|
+
|
|
161
|
+
# Verify global exposure code is present
|
|
162
|
+
# Note: Variable names may be minified, so we check for the concept rather than exact strings
|
|
163
|
+
self.assertIn("__jacEnsureHydration", bundle.code)
|
|
164
|
+
self.assertIn("globalThis.start_app()", bundle.code)
|
|
165
|
+
|
|
166
|
+
# Cleanup
|
|
167
|
+
builder.cleanup_temp_dir()
|
|
168
|
+
|
|
169
|
+
def test_build_bundle_with_antd(self) -> None:
|
|
170
|
+
"""Test that Vite bundling works with Ant Design components."""
|
|
171
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
172
|
+
temp_path = Path(temp_dir)
|
|
173
|
+
|
|
174
|
+
# Create project with Vite and Ant Design installed
|
|
175
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path, include_antd=True)
|
|
176
|
+
|
|
177
|
+
# Initialize the Vite builder
|
|
178
|
+
builder = ViteClientBundleBuilder(
|
|
179
|
+
vite_package_json=package_json,
|
|
180
|
+
vite_output_dir=output_dir,
|
|
181
|
+
vite_minify=False,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Import the test module with Ant Design
|
|
185
|
+
fixtures_dir = Path(__file__).parent / "fixtures"
|
|
186
|
+
(module,) = Jac.jac_import("client_app_with_antd", str(fixtures_dir))
|
|
187
|
+
|
|
188
|
+
# Build the bundle
|
|
189
|
+
bundle = builder.build(module, force=True)
|
|
190
|
+
|
|
191
|
+
# Verify bundle structure
|
|
192
|
+
self.assertIsNotNone(bundle)
|
|
193
|
+
self.assertEqual(bundle.module_name, "client_app_with_antd")
|
|
194
|
+
self.assertIn("ButtonTest", bundle.client_functions)
|
|
195
|
+
self.assertIn("CardTest", bundle.client_functions)
|
|
196
|
+
self.assertIn("APP_NAME", bundle.client_globals)
|
|
197
|
+
|
|
198
|
+
# Verify bundle code contains expected content
|
|
199
|
+
self.assertIn("function ButtonTest()", bundle.code)
|
|
200
|
+
self.assertIn("function CardTest()", bundle.code)
|
|
201
|
+
self.assertIn('const APP_NAME = "Ant Design Test";', bundle.code)
|
|
202
|
+
|
|
203
|
+
# verify antd components are present
|
|
204
|
+
self.assertIn("ButtonGroup", bundle.code)
|
|
205
|
+
|
|
206
|
+
# Verify the Ant Design fixture content is present
|
|
207
|
+
self.assertIn("Testing Ant Design integration", bundle.code)
|
|
208
|
+
|
|
209
|
+
# Verify bundle was written to output directory
|
|
210
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
211
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
212
|
+
|
|
213
|
+
# Cleanup
|
|
214
|
+
builder.cleanup_temp_dir()
|
|
215
|
+
|
|
216
|
+
def test_relative_import(self) -> None:
|
|
217
|
+
"""Test that relative imports work correctly in Vite bundling."""
|
|
218
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
219
|
+
temp_path = Path(temp_dir)
|
|
220
|
+
|
|
221
|
+
# Create project with Vite installed
|
|
222
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path, include_antd=True)
|
|
223
|
+
|
|
224
|
+
# Initialize the Vite builder
|
|
225
|
+
builder = ViteClientBundleBuilder(
|
|
226
|
+
vite_package_json=package_json,
|
|
227
|
+
vite_output_dir=output_dir,
|
|
228
|
+
vite_minify=False,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Import the test module with relative import
|
|
232
|
+
fixtures_dir = Path(__file__).parent / "fixtures"
|
|
233
|
+
(module,) = Jac.jac_import("relative_import", str(fixtures_dir))
|
|
234
|
+
|
|
235
|
+
# Build the bundle
|
|
236
|
+
bundle = builder.build(module, force=True)
|
|
237
|
+
|
|
238
|
+
# Verify bundle structure
|
|
239
|
+
self.assertIsNotNone(bundle)
|
|
240
|
+
self.assertEqual(bundle.module_name, "relative_import")
|
|
241
|
+
self.assertIn("RelativeImport", bundle.client_functions)
|
|
242
|
+
self.assertIn("main", bundle.client_functions)
|
|
243
|
+
self.assertIn("CustomButton", bundle.code)
|
|
244
|
+
|
|
245
|
+
# Verify bundle code contains expected content
|
|
246
|
+
self.assertIn("function RelativeImport()", bundle.code)
|
|
247
|
+
self.assertIn("function main()", bundle.code)
|
|
248
|
+
|
|
249
|
+
# Verify that the relative import (Button from .button) is properly resolved
|
|
250
|
+
self.assertIn("ButtonGroup", bundle.code)
|
|
251
|
+
|
|
252
|
+
# Verify the Jac initialization is present
|
|
253
|
+
self.assertIn("__jacRegisterClientModule", bundle.code)
|
|
254
|
+
|
|
255
|
+
# Verify bundle was written to output directory
|
|
256
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
257
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
258
|
+
|
|
259
|
+
# Cleanup
|
|
260
|
+
builder.cleanup_temp_dir()
|
|
261
|
+
|
|
262
|
+
def test_js_import(self) -> None:
|
|
263
|
+
"""Test that JavaScript file imports work correctly in Vite bundling."""
|
|
264
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
265
|
+
temp_path = Path(temp_dir)
|
|
266
|
+
# Create project with Vite installed
|
|
267
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path)
|
|
268
|
+
|
|
269
|
+
# Initialize the Vite builder
|
|
270
|
+
builder = ViteClientBundleBuilder(
|
|
271
|
+
vite_package_json=package_json,
|
|
272
|
+
vite_output_dir=output_dir,
|
|
273
|
+
vite_minify=False,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Import the test module with JavaScript import
|
|
277
|
+
fixtures_dir = Path(__file__).parent / "fixtures"
|
|
278
|
+
(module,) = Jac.jac_import("js_import", str(fixtures_dir))
|
|
279
|
+
|
|
280
|
+
# Build the bundle
|
|
281
|
+
bundle = builder.build(module, force=True)
|
|
282
|
+
|
|
283
|
+
# Verify bundle structure
|
|
284
|
+
self.assertIsNotNone(bundle)
|
|
285
|
+
self.assertEqual(bundle.module_name, "js_import")
|
|
286
|
+
self.assertIn("JsImportTest", bundle.client_functions)
|
|
287
|
+
self.assertIn("Main", bundle.client_functions)
|
|
288
|
+
self.assertIn("JS_IMPORT_LABEL", bundle.client_globals)
|
|
289
|
+
|
|
290
|
+
# Verify bundle code contains expected content
|
|
291
|
+
self.assertIn("function JsImportTest()", bundle.code)
|
|
292
|
+
self.assertIn("function Main()", bundle.code)
|
|
293
|
+
self.assertIn('const JS_IMPORT_LABEL = "JavaScript Import Test";', bundle.code)
|
|
294
|
+
|
|
295
|
+
# Verify JavaScript imports are present in the bundle
|
|
296
|
+
# The JavaScript functions should be available in the bundle
|
|
297
|
+
self.assertIn("formatMessage", bundle.code)
|
|
298
|
+
self.assertIn("calculateSum", bundle.code)
|
|
299
|
+
self.assertIn("JS_CONSTANT", bundle.code)
|
|
300
|
+
self.assertIn("MessageFormatter", bundle.code)
|
|
301
|
+
|
|
302
|
+
# Verify the JavaScript utility code is included
|
|
303
|
+
self.assertIn("Hello,", bundle.code) # From formatMessage function
|
|
304
|
+
self.assertIn("Imported from JavaScript", bundle.code) # From JS_CONSTANT
|
|
305
|
+
|
|
306
|
+
# Verify the Jac initialization is present
|
|
307
|
+
self.assertIn("__jacRegisterClientModule", bundle.code)
|
|
308
|
+
|
|
309
|
+
# Verify bundle was written to output directory
|
|
310
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
311
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
312
|
+
|
|
313
|
+
# Cleanup
|
|
314
|
+
builder.cleanup_temp_dir()
|
|
315
|
+
|
|
316
|
+
def test_jsx_fragments_and_spread_props(self) -> None:
|
|
317
|
+
"""Test that JSX fragments and spread props work correctly."""
|
|
318
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
319
|
+
temp_path = Path(temp_dir)
|
|
320
|
+
|
|
321
|
+
# Create project with Vite installed
|
|
322
|
+
package_json, output_dir = self._create_test_project_with_vite(temp_path)
|
|
323
|
+
|
|
324
|
+
# Initialize the Vite builder
|
|
325
|
+
builder = ViteClientBundleBuilder(
|
|
326
|
+
vite_package_json=package_json,
|
|
327
|
+
vite_output_dir=output_dir,
|
|
328
|
+
vite_minify=False,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Import the test module with fragments and spread props
|
|
332
|
+
fixtures_dir = Path(__file__).parent / "fixtures"
|
|
333
|
+
(module,) = Jac.jac_import("test_fragments_spread", str(fixtures_dir))
|
|
334
|
+
|
|
335
|
+
# Build the bundle
|
|
336
|
+
bundle = builder.build(module, force=True)
|
|
337
|
+
|
|
338
|
+
# Verify bundle structure
|
|
339
|
+
self.assertIsNotNone(bundle)
|
|
340
|
+
self.assertEqual(bundle.module_name, "test_fragments_spread")
|
|
341
|
+
self.assertIn("FragmentTest", bundle.client_functions)
|
|
342
|
+
self.assertIn("SpreadPropsTest", bundle.client_functions)
|
|
343
|
+
self.assertIn("MixedTest", bundle.client_functions)
|
|
344
|
+
self.assertIn("NestedFragments", bundle.client_functions)
|
|
345
|
+
|
|
346
|
+
# Verify spread props handling (Object.assign is used by compiler)
|
|
347
|
+
self.assertIn("Object.assign", bundle.code)
|
|
348
|
+
|
|
349
|
+
# Verify fragment test function exists
|
|
350
|
+
self.assertIn("function FragmentTest()", bundle.code)
|
|
351
|
+
|
|
352
|
+
# Verify spread props test function exists
|
|
353
|
+
self.assertIn("function SpreadPropsTest()", bundle.code)
|
|
354
|
+
|
|
355
|
+
# Verify bundle was written to output directory
|
|
356
|
+
bundle_files = list(output_dir.glob("client.*.js"))
|
|
357
|
+
self.assertGreater(len(bundle_files), 0, "Expected at least one bundle file")
|
|
358
|
+
|
|
359
|
+
# Cleanup
|
|
360
|
+
builder.cleanup_temp_dir()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Test create-jac-app command."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from subprocess import run
|
|
7
|
+
from unittest import TestCase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestCreateJacApp(TestCase):
|
|
11
|
+
"""Test create-jac-app command functionality."""
|
|
12
|
+
|
|
13
|
+
def test_create_jac_app(self) -> None:
|
|
14
|
+
"""Test create-jac-app command."""
|
|
15
|
+
test_project_name = "test-jac-app"
|
|
16
|
+
|
|
17
|
+
# Create a temporary directory for testing
|
|
18
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
19
|
+
original_cwd = os.getcwd()
|
|
20
|
+
try:
|
|
21
|
+
# Change to temp directory
|
|
22
|
+
os.chdir(temp_dir)
|
|
23
|
+
|
|
24
|
+
# Run create-jac-app command
|
|
25
|
+
result = run(
|
|
26
|
+
[
|
|
27
|
+
"jac",
|
|
28
|
+
"create_jac_app",
|
|
29
|
+
test_project_name
|
|
30
|
+
],
|
|
31
|
+
capture_output=True,
|
|
32
|
+
text=True,
|
|
33
|
+
check=True
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Check that command succeeded
|
|
37
|
+
self.assertEqual(result.returncode, 0)
|
|
38
|
+
self.assertIn(f"Successfully created Jac application '{test_project_name}'!", result.stdout)
|
|
39
|
+
|
|
40
|
+
# Verify project directory was created
|
|
41
|
+
project_path = os.path.join(temp_dir, test_project_name)
|
|
42
|
+
self.assertTrue(os.path.exists(project_path))
|
|
43
|
+
self.assertTrue(os.path.isdir(project_path))
|
|
44
|
+
|
|
45
|
+
# Verify package.json was created and has correct content
|
|
46
|
+
package_json_path = os.path.join(project_path, "package.json")
|
|
47
|
+
self.assertTrue(os.path.exists(package_json_path))
|
|
48
|
+
|
|
49
|
+
with open(package_json_path, "r") as f:
|
|
50
|
+
package_data = json.load(f)
|
|
51
|
+
|
|
52
|
+
self.assertEqual(package_data["name"], test_project_name)
|
|
53
|
+
self.assertEqual(package_data["type"], "module")
|
|
54
|
+
self.assertIn("vite", package_data["devDependencies"])
|
|
55
|
+
self.assertIn("build", package_data["scripts"])
|
|
56
|
+
self.assertIn("dev", package_data["scripts"])
|
|
57
|
+
self.assertIn("preview", package_data["scripts"])
|
|
58
|
+
|
|
59
|
+
# Verify app.jac file was created
|
|
60
|
+
app_jac_path = os.path.join(project_path, "app.jac")
|
|
61
|
+
self.assertTrue(os.path.exists(app_jac_path))
|
|
62
|
+
|
|
63
|
+
with open(app_jac_path, "r") as f:
|
|
64
|
+
app_jac_content = f.read()
|
|
65
|
+
|
|
66
|
+
self.assertIn("jac_app()", app_jac_content)
|
|
67
|
+
|
|
68
|
+
# Verify README.md was created
|
|
69
|
+
readme_path = os.path.join(project_path, "README.md")
|
|
70
|
+
self.assertTrue(os.path.exists(readme_path))
|
|
71
|
+
|
|
72
|
+
with open(readme_path, "r") as f:
|
|
73
|
+
readme_content = f.read()
|
|
74
|
+
|
|
75
|
+
self.assertIn(f"# {test_project_name}", readme_content)
|
|
76
|
+
self.assertIn("jac serve app.jac", readme_content)
|
|
77
|
+
|
|
78
|
+
# Verify node_modules was created (npm install ran)
|
|
79
|
+
node_modules_path = os.path.join(project_path, "node_modules")
|
|
80
|
+
self.assertTrue(os.path.exists(node_modules_path))
|
|
81
|
+
|
|
82
|
+
finally:
|
|
83
|
+
# Return to original directory
|
|
84
|
+
os.chdir(original_cwd)
|
|
85
|
+
|
|
86
|
+
def test_create_jac_app_invalid_name(self) -> None:
|
|
87
|
+
"""Test create-jac-app command with invalid project name."""
|
|
88
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
89
|
+
original_cwd = os.getcwd()
|
|
90
|
+
try:
|
|
91
|
+
os.chdir(temp_dir)
|
|
92
|
+
|
|
93
|
+
# Test with invalid name containing spaces
|
|
94
|
+
result = run(
|
|
95
|
+
[
|
|
96
|
+
"jac",
|
|
97
|
+
"create_jac_app",
|
|
98
|
+
"invalid name with spaces"
|
|
99
|
+
],
|
|
100
|
+
capture_output=True,
|
|
101
|
+
text=True
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Should fail with non-zero exit code
|
|
105
|
+
self.assertNotEqual(result.returncode, 0)
|
|
106
|
+
self.assertIn("Project name must contain only letters, numbers, hyphens, and underscores", result.stderr)
|
|
107
|
+
|
|
108
|
+
finally:
|
|
109
|
+
os.chdir(original_cwd)
|
|
110
|
+
|
|
111
|
+
def test_create_jac_app_existing_directory(self) -> None:
|
|
112
|
+
"""Test create-jac-app command when directory already exists."""
|
|
113
|
+
test_project_name = "existing-test-app"
|
|
114
|
+
|
|
115
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
116
|
+
original_cwd = os.getcwd()
|
|
117
|
+
try:
|
|
118
|
+
os.chdir(temp_dir)
|
|
119
|
+
|
|
120
|
+
# Create the directory first
|
|
121
|
+
os.makedirs(test_project_name)
|
|
122
|
+
|
|
123
|
+
# Try to create app with same name
|
|
124
|
+
result = run(
|
|
125
|
+
[
|
|
126
|
+
"jac",
|
|
127
|
+
"create_jac_app",
|
|
128
|
+
test_project_name
|
|
129
|
+
],
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Should fail with non-zero exit code
|
|
135
|
+
self.assertNotEqual(result.returncode, 0)
|
|
136
|
+
self.assertIn(f"Directory '{test_project_name}' already exists", result.stderr)
|
|
137
|
+
|
|
138
|
+
finally:
|
|
139
|
+
os.chdir(original_cwd)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jac-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Jason Mars
|
|
6
|
+
Author-email: jason@mars.ninja
|
|
7
|
+
Requires-Python: >=3.12.0,<4.0.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Requires-Dist: jaclang (==0.8.10)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Jac Client
|
|
16
|
+
|
|
17
|
+
Build full-stack web applications with Jac - one language for frontend and backend.
|
|
18
|
+
|
|
19
|
+
Jac Client enables you to write React-like components, manage state, and build interactive UIs all in Jac. No need for separate frontend frameworks, HTTP clients, or complex build configurations.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ✨ Features
|
|
24
|
+
|
|
25
|
+
- **Single Language**: Write frontend and backend in Jac
|
|
26
|
+
- **No HTTP Client**: Use `__jacSpawn()` instead of fetch/axios
|
|
27
|
+
- **Reactive State**: Built-in state management with `createState()`
|
|
28
|
+
- **Component-Based**: Build reusable UI components with JSX
|
|
29
|
+
- **Graph Database**: Built-in graph data model eliminates need for SQL/NoSQL
|
|
30
|
+
- **Type Safety**: Type checking across frontend and backend
|
|
31
|
+
- **Vite-Powered**: Optimized production bundles with Vite
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🚀 Quick Start
|
|
36
|
+
|
|
37
|
+
### Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install jac-client
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Create a New App
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
jac create_jac_app my-app
|
|
47
|
+
cd my-app
|
|
48
|
+
jac serve app.jac
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Visit `http://localhost:8000` to see your app!
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 📚 Documentation
|
|
56
|
+
|
|
57
|
+
For detailed guides and tutorials, see the **[docs folder](jac_client/docs/)**:
|
|
58
|
+
|
|
59
|
+
- **[Getting Started Guide](jac_client/docs/README.md)** - Complete beginner's guide
|
|
60
|
+
- **[Routing](jac_client/docs/routing.md)** - Multi-page applications with `initRouter()`
|
|
61
|
+
- **[Lifecycle Hooks](jac_client/docs/lifecycle-hooks.md)** - Using `onMount()` for initialization
|
|
62
|
+
- **[Advanced State](jac_client/docs/advanced-state.md)** - Managing complex state
|
|
63
|
+
- **[Imports](jac_client/docs/imports.md)** - Importing libraries, Jac files, and JavaScript modules
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 💡 Example
|
|
68
|
+
|
|
69
|
+
```jac
|
|
70
|
+
cl {
|
|
71
|
+
let [count, setCount] = createState({"value": 0});
|
|
72
|
+
|
|
73
|
+
def Counter() -> any {
|
|
74
|
+
s = count();
|
|
75
|
+
return <div>
|
|
76
|
+
<h1>Count: {s.value}</h1>
|
|
77
|
+
<button onClick={lambda -> None {
|
|
78
|
+
setCount({"value": s.value + 1});
|
|
79
|
+
}}>
|
|
80
|
+
Increment
|
|
81
|
+
</button>
|
|
82
|
+
</div>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
def jac_app() -> any {
|
|
86
|
+
return Counter();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 🔧 Requirements
|
|
94
|
+
|
|
95
|
+
- Python: 3.12+
|
|
96
|
+
- Node.js: For npm and Vite
|
|
97
|
+
- Jac Language: `jaclang` (installed automatically)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🛠️ How It Works
|
|
102
|
+
|
|
103
|
+
Jac Client is a plugin that:
|
|
104
|
+
1. Compiles your `.jac` client code to JavaScript
|
|
105
|
+
2. Bundles dependencies with Vite for optimal performance
|
|
106
|
+
3. Provides a runtime for reactive state and components
|
|
107
|
+
4. Integrates seamlessly with Jac's backend graph operations
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 📖 Learn More
|
|
112
|
+
|
|
113
|
+
- **Full Documentation**: See [docs/](jac_client/docs/) for comprehensive guides
|
|
114
|
+
- **Examples**: Check `jac_client/examples/` for working examples
|
|
115
|
+
- **Issues**: Report bugs on [GitHub Issues](https://github.com/Jaseci-Labs/jaseci/issues)
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 📄 License
|
|
120
|
+
|
|
121
|
+
MIT License - see [LICENSE](../LICENSE) file.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
**Happy coding with Jac!** 🎉
|
|
126
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
jac_client/docs/README.md,sha256=LM1d3OphsarGMmUtcdCFzu5eXkQTThofZuscfnmORzY,15435
|
|
2
|
+
jac_client/docs/advanced-state.md,sha256=XpZwyXcFQNfcTdk5H9Cxcr-QhVWw8X9xcNIGpxXh13k,17107
|
|
3
|
+
jac_client/docs/imports.md,sha256=hBIZLsIZoMMCajwRlvgGtjYk2WqVz9GkPqHlHonAnzE,12468
|
|
4
|
+
jac_client/docs/lifecycle-hooks.md,sha256=Yg6Fuzgj5ouq0kA4KDU_x8-eFY95etyXP3ykAfoxkMk,12497
|
|
5
|
+
jac_client/docs/routing.md,sha256=kIwJmy9UzuxkKpj8yuHWNYHceEZ1Zi8H_c6bRwXEG2M,13661
|
|
6
|
+
jac_client/examples/little-x/app.jac,sha256=NueM8h35Bvf-hXL-kyeZ01-HAL7xZxUxrpHR99ASkhM,16760
|
|
7
|
+
jac_client/examples/little-x/package-lock.json,sha256=wlaWHbdT4gt8g6EIZLkgRqLOuoX4DgquNIYCXk46M6g,99176
|
|
8
|
+
jac_client/examples/little-x/package.json,sha256=XaXpijPuRDojT-yQFJwkaopDgqMiriepqEk0LsFQ-cU,521
|
|
9
|
+
jac_client/examples/little-x/submit-button.jac,sha256=ByyRpDDN49RxoF8k7TC0r1mgoKAwwdlpo6wiiTE7p_4,324
|
|
10
|
+
jac_client/examples/todo-app/README.md,sha256=eXQmqm63kuiNLF-FbHzE4oPQL26MiWu5_fvfc05X_iw,2101
|
|
11
|
+
jac_client/examples/todo-app/app.jac,sha256=HQML_uFKFIY03EnSeat-xQKafnfzj1lQzu46m9QeWLs,25628
|
|
12
|
+
jac_client/examples/todo-app/package-lock.json,sha256=56PSCXFx3qpwFpyyLf1sliNXobpvQPNfUDHYbOoRktE,31170
|
|
13
|
+
jac_client/examples/todo-app/package.json,sha256=gGOWdeCfPAl6g8pjGVIuPUuEm8h1XUTjF1r6MeuXiN4,413
|
|
14
|
+
jac_client/plugin/cli.py,sha256=4hB84MQ_3o2sWxWKIvmPURxnh_Kpt4iSrgoHHpFRRdA,10938
|
|
15
|
+
jac_client/plugin/client.py,sha256=76fqmTcfHBOXGs2hcsb3Wq09L5GW0r-5-9JGdvU_Nhs,1277
|
|
16
|
+
jac_client/plugin/client_runtime.jac,sha256=kjaODlNKjCbE-fXzkSYGN4k-SwmxsvBd4qQk9ZvYTGI,27507
|
|
17
|
+
jac_client/plugin/vite_client_bundle.py,sha256=1sQwtd9VgLd3cutidRR9DJMiiKahBlWzhWilhXPwmtA,18074
|
|
18
|
+
jac_client/tests/__init__.py,sha256=zleq_UtH4HBbp9SSkkqtaLd1tvm5F3QfXHEffXpxxQo,37
|
|
19
|
+
jac_client/tests/fixtures/button.jac,sha256=8xLhhX7UpiRdzO9BLC1XwhejiKMEi50VlPFSjJP0XR8,109
|
|
20
|
+
jac_client/tests/fixtures/client_app.jac,sha256=ReEvwsPHEh56RfSmW_KCM9sk6jjr5QnSENMUJHZfMVs,445
|
|
21
|
+
jac_client/tests/fixtures/client_app_with_antd.jac,sha256=e-BK4TVlw8OKiCxIS0tH1-oJkWNEon-eMaJXvBvX39M,399
|
|
22
|
+
jac_client/tests/fixtures/js_import.jac,sha256=GmAMLEFTp-CfoQ4G2JmFpD4boKpYh8JTC9LZRX5CieM,721
|
|
23
|
+
jac_client/tests/fixtures/package-lock.json,sha256=RKNoyMoGXx3IOm3B5d1Kt0vrgMac9intCLXGew51AAQ,19358
|
|
24
|
+
jac_client/tests/fixtures/package.json,sha256=s5Ltvbh1MQGeZZyyNJdqhoA21VO5F7scBcVPlgHpF3c,278
|
|
25
|
+
jac_client/tests/fixtures/relative_import.jac,sha256=3ZDq-eg4BIdULklet-Enic-AVRFVnAbBkQwOOmfFbaM,190
|
|
26
|
+
jac_client/tests/fixtures/test_fragments_spread.jac,sha256=z8Hs6N7HV8dE7jrW0NZtUt7Tbuo-bWkYBW4O8N7A-Rw,964
|
|
27
|
+
jac_client/tests/fixtures/utils.js,sha256=lrcZ9yUEb4QrpUpUp0gWRvISFui9_v96rWPN-btllmU,413
|
|
28
|
+
jac_client/tests/test_cl.py,sha256=4Ulr-5plEX3PDuVnIaMfFUcfQj3l0YrWd5ExNybpnIo,15398
|
|
29
|
+
jac_client/tests/test_create_jac_app.py,sha256=UXDFYAjYHoRbNSfT0pfwVqXZ3h0cBMQ_nXevB0BpDtI,5518
|
|
30
|
+
jac_client-0.1.0.dist-info/METADATA,sha256=mtXw8sK4Ac3Hc1IWp6bS9T9ImiW07l5Uk24UDZGWl1I,3202
|
|
31
|
+
jac_client-0.1.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
32
|
+
jac_client-0.1.0.dist-info/entry_points.txt,sha256=WxzPDwDUGAvQyJ_RkIOSk3cJgJChESQGP839Kb70_vo,92
|
|
33
|
+
jac_client-0.1.0.dist-info/RECORD,,
|