gelang 0.1.0

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 (96) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +535 -0
  4. package/bin/ge.js +112 -0
  5. package/package.json +62 -0
  6. package/python/pyeffic/__init__.py +9 -0
  7. package/python/pyeffic/__main__.py +6 -0
  8. package/python/pyeffic/analyzer.py +464 -0
  9. package/python/pyeffic/apisurface.py +238 -0
  10. package/python/pyeffic/autoselect.py +327 -0
  11. package/python/pyeffic/backends.py +87 -0
  12. package/python/pyeffic/bench.py +233 -0
  13. package/python/pyeffic/cli.py +184 -0
  14. package/python/pyeffic/compiler.py +421 -0
  15. package/python/pyeffic/config.py +383 -0
  16. package/python/pyeffic/dartgen.py +441 -0
  17. package/python/pyeffic/deploy.py +586 -0
  18. package/python/pyeffic/diagnostics.py +194 -0
  19. package/python/pyeffic/difftest.py +424 -0
  20. package/python/pyeffic/downloader.py +307 -0
  21. package/python/pyeffic/emitters/__init__.py +11 -0
  22. package/python/pyeffic/emitters/base.py +2359 -0
  23. package/python/pyeffic/emitters/cpp.py +266 -0
  24. package/python/pyeffic/emitters/csharp.py +342 -0
  25. package/python/pyeffic/emitters/dart.py +349 -0
  26. package/python/pyeffic/emitters/go.py +388 -0
  27. package/python/pyeffic/emitters/kotlin.py +314 -0
  28. package/python/pyeffic/emitters/rust.py +314 -0
  29. package/python/pyeffic/emitters/zig.py +411 -0
  30. package/python/pyeffic/ffi.py +49 -0
  31. package/python/pyeffic/frontends/__init__.py +94 -0
  32. package/python/pyeffic/frontends/hybrid.py +709 -0
  33. package/python/pyeffic/frontends/typescript.py +965 -0
  34. package/python/pyeffic/ge_cli.py +1148 -0
  35. package/python/pyeffic/golden.py +348 -0
  36. package/python/pyeffic/idents.py +206 -0
  37. package/python/pyeffic/modules.py +220 -0
  38. package/python/pyeffic/packer.py +222 -0
  39. package/python/pyeffic/pipeline.py +797 -0
  40. package/python/pyeffic/reactgen.py +966 -0
  41. package/python/pyeffic/researcher.py +177 -0
  42. package/python/pyeffic/scaffold.py +397 -0
  43. package/python/pyeffic/stdlib.py +246 -0
  44. package/python/pyeffic/styling.py +220 -0
  45. package/python/pyeffic/templates/desktop_gui/README.md +106 -0
  46. package/python/pyeffic/templates/desktop_gui/app/__init__.py +0 -0
  47. package/python/pyeffic/templates/desktop_gui/app/core/__init__.py +0 -0
  48. package/python/pyeffic/templates/desktop_gui/app/core/add.ge.py +13 -0
  49. package/python/pyeffic/templates/desktop_gui/app/core/factorial.ge.py +20 -0
  50. package/python/pyeffic/templates/desktop_gui/app/core/fibonacci.ge.py +25 -0
  51. package/python/pyeffic/templates/desktop_gui/app/core/gcd.ge.py +19 -0
  52. package/python/pyeffic/templates/desktop_gui/app/core/is_prime.ge.py +24 -0
  53. package/python/pyeffic/templates/desktop_gui/app/core/multiply.ge.py +13 -0
  54. package/python/pyeffic/templates/desktop_gui/app/core/power.ge.py +25 -0
  55. package/python/pyeffic/templates/desktop_gui/app/main.ge.py +49 -0
  56. package/python/pyeffic/templates/desktop_gui/app/memory/__init__.py +0 -0
  57. package/python/pyeffic/templates/desktop_gui/app/memory/buffer.ge.py +26 -0
  58. package/python/pyeffic/templates/desktop_gui/app/memory/limits.ge.py +47 -0
  59. package/python/pyeffic/templates/desktop_gui/app/memory/state.ge.py +44 -0
  60. package/python/pyeffic/templates/desktop_gui/app/ui/__init__.py +0 -0
  61. package/python/pyeffic/templates/desktop_gui/app/ui/layout.ge.py +64 -0
  62. package/python/pyeffic/templates/desktop_gui/app/ui/render.ge.py +87 -0
  63. package/python/pyeffic/templates/desktop_gui/app/ui/theme.ge.py +147 -0
  64. package/python/pyeffic/templates/desktop_gui/app/ui/widgets.ge.py +105 -0
  65. package/python/pyeffic/templates/desktop_gui/desktop/__init__.py +1 -0
  66. package/python/pyeffic/templates/desktop_gui/desktop/main.ge.py +258 -0
  67. package/python/pyeffic/templates/desktop_gui/ge.toml +16 -0
  68. package/python/pyeffic/templates/desktop_gui/tests/__init__.py +0 -0
  69. package/python/pyeffic/templates/desktop_gui/tests/ge_loader.py +76 -0
  70. package/python/pyeffic/templates/desktop_gui/tests/test_app.py +173 -0
  71. package/python/pyeffic/templates/web_react/README.md +115 -0
  72. package/python/pyeffic/templates/web_react/app/__init__.py +0 -0
  73. package/python/pyeffic/templates/web_react/app/core/__init__.py +0 -0
  74. package/python/pyeffic/templates/web_react/app/core/add.ge.py +9 -0
  75. package/python/pyeffic/templates/web_react/app/core/factorial.ge.py +16 -0
  76. package/python/pyeffic/templates/web_react/app/core/fibonacci.ge.py +21 -0
  77. package/python/pyeffic/templates/web_react/app/core/is_prime.ge.py +20 -0
  78. package/python/pyeffic/templates/web_react/app/core/multiply.ge.py +9 -0
  79. package/python/pyeffic/templates/web_react/app/main.ge.py +25 -0
  80. package/python/pyeffic/templates/web_react/app/memory/__init__.py +0 -0
  81. package/python/pyeffic/templates/web_react/app/memory/buffer.ge.py +25 -0
  82. package/python/pyeffic/templates/web_react/app/memory/limits.ge.py +51 -0
  83. package/python/pyeffic/templates/web_react/ge.toml +23 -0
  84. package/python/pyeffic/templates/web_react/tests/__init__.py +0 -0
  85. package/python/pyeffic/templates/web_react/tests/ge_loader.py +68 -0
  86. package/python/pyeffic/templates/web_react/tests/test_app.py +105 -0
  87. package/python/pyeffic/templates/web_react/ui/main.ge.ui +33 -0
  88. package/python/pyeffic/templates/web_react/web/__init__.py +0 -0
  89. package/python/pyeffic/templates/web_react/web/server.ge.py +78 -0
  90. package/python/pyeffic/ts2py.py +657 -0
  91. package/python/pyeffic/typecheck.py +232 -0
  92. package/python/pyeffic/ui.py +154 -0
  93. package/python/pyeffic/ui_dsl.py +618 -0
  94. package/python/pyeffic/widgets.py +87 -0
  95. package/scripts/README.md +42 -0
  96. package/scripts/check-toolchains.py +85 -0
@@ -0,0 +1,586 @@
1
+ """GE deployment system — ship self-contained .ge packages to VPS or local.
2
+
3
+ A .ge package is self-contained: it includes the native backend library,
4
+ Dart/Flutter UI, and a deployment manifest. When deployed:
5
+
6
+ 1. Auto-detect: scan the package manifest to determine what's inside
7
+ (backend, web, mobile, desktop components)
8
+ 2. Distribute: place each component in the correct location
9
+ - backend/ → native shared library (serves API or FFI)
10
+ - web/ → static files served by the embedded HTTP server
11
+ - mobile/ → Flutter app bundle
12
+ - desktop/ → native executable
13
+ 3. Simulate: run a pre-flight simulation test before going live
14
+ - verify all components are present
15
+ - verify the native library loads
16
+ - verify the HTTP server starts and responds
17
+ - verify FFI bindings resolve
18
+ 4. Go live: start the server / launch the app
19
+
20
+ Usage:
21
+ ge deploy myapp.ge --target vps --host user@server.com
22
+ ge deploy myapp.ge --target local --port 8080
23
+ ge deploy myapp.ge --target simulate # simulation only, no live deploy
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import os
29
+ import shutil
30
+ import subprocess
31
+ import sys
32
+ import tempfile
33
+ import time
34
+ from pathlib import Path
35
+ from dataclasses import dataclass, field
36
+ from typing import Any
37
+
38
+
39
+ @dataclass
40
+ class DeployManifest:
41
+ """Manifest describing what's in a .ge package and how to deploy it."""
42
+ app_name: str = ""
43
+ version: str = "0.1.0"
44
+ backend: str = "rust"
45
+ components: list[str] = field(default_factory=list) # backend, web, mobile, desktop
46
+ ffi_exports: list[str] = field(default_factory=list)
47
+ entry_point: str = "main"
48
+ native_libs: dict[str, str] = field(default_factory=dict) # backend name -> lib path
49
+ dart_files: list[str] = field(default_factory=list)
50
+ has_web: bool = False
51
+ has_mobile: bool = False
52
+ has_desktop: bool = False
53
+ has_backend: bool = False
54
+ port: int = 8080
55
+ host: str = "0.0.0.0"
56
+
57
+ def to_dict(self) -> dict:
58
+ return {
59
+ "app_name": self.app_name,
60
+ "version": self.version,
61
+ "backend": self.backend,
62
+ "components": self.components,
63
+ "ffi_exports": self.ffi_exports,
64
+ "entry_point": self.entry_point,
65
+ "native_libs": self.native_libs,
66
+ "dart_files": self.dart_files,
67
+ "has_web": self.has_web,
68
+ "has_mobile": self.has_mobile,
69
+ "has_desktop": self.has_desktop,
70
+ "has_backend": self.has_backend,
71
+ "port": self.port,
72
+ "host": self.host,
73
+ }
74
+
75
+ @classmethod
76
+ def from_dict(cls, d: dict) -> "DeployManifest":
77
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
78
+
79
+
80
+ def detect_manifest(staging_dir: Path, meta: Any = None) -> DeployManifest:
81
+ """Auto-detect what's in an unpacked .ge package by scanning its contents.
82
+
83
+ Args:
84
+ staging_dir: directory where the .ge package was unpacked
85
+ meta: optional PackageMeta from the .ge file
86
+
87
+ Returns:
88
+ DeployManifest describing the package contents.
89
+ """
90
+ manifest = DeployManifest()
91
+
92
+ # Get app name and version from meta if available
93
+ if meta:
94
+ manifest.app_name = getattr(meta, "app_name", "") or "ge_app"
95
+ manifest.version = getattr(meta, "version", "0.1.0")
96
+ manifest.backend = getattr(meta, "backend", "rust")
97
+ manifest.ffi_exports = list(getattr(meta, "ffi_exports", []))
98
+
99
+ # Scan for native libraries (backend/)
100
+ native_dir = staging_dir / "native"
101
+ if native_dir.exists():
102
+ manifest.has_backend = True
103
+ manifest.components.append("backend")
104
+ for backend_dir in native_dir.iterdir():
105
+ if backend_dir.is_dir():
106
+ for f in backend_dir.iterdir():
107
+ if f.suffix in (".dll", ".so", ".dylib", ".a"):
108
+ manifest.native_libs[backend_dir.name] = str(f)
109
+ elif f.suffix in (".exe", "") and f.is_file():
110
+ # Native executable (desktop)
111
+ manifest.has_desktop = True
112
+ if "desktop" not in manifest.components:
113
+ manifest.components.append("desktop")
114
+ manifest.native_libs[backend_dir.name] = str(f)
115
+
116
+ # Scan for Dart/Flutter files (mobile or web)
117
+ dart_dir = staging_dir / "dart"
118
+ if dart_dir.exists():
119
+ dart_files = list(dart_dir.glob("*.dart"))
120
+ if dart_files:
121
+ manifest.dart_files = [f.name for f in dart_files]
122
+ # Check if it's a web or mobile Flutter app
123
+ main_dart = dart_dir / "main.dart"
124
+ if main_dart.exists():
125
+ content = main_dart.read_text(encoding="utf-8", errors="ignore")
126
+ if "flutter" in content.lower() or "runApp" in content:
127
+ manifest.has_mobile = True
128
+ if "mobile" not in manifest.components:
129
+ manifest.components.append("mobile")
130
+ if "html" in content.lower() or "web" in content.lower():
131
+ manifest.has_web = True
132
+ if "web" not in manifest.components:
133
+ manifest.components.append("web")
134
+
135
+ # Check for explicit web files
136
+ web_dir = staging_dir / "web"
137
+ if web_dir.exists():
138
+ manifest.has_web = True
139
+ if "web" not in manifest.components:
140
+ manifest.components.append("web")
141
+
142
+ # Check for explicit desktop files
143
+ desktop_dir = staging_dir / "desktop"
144
+ if desktop_dir.exists():
145
+ manifest.has_desktop = True
146
+ if "desktop" not in manifest.components:
147
+ manifest.components.append("desktop")
148
+
149
+ return manifest
150
+
151
+
152
+ def distribute_components(staging_dir: Path, deploy_dir: Path,
153
+ manifest: DeployManifest) -> dict[str, Path]:
154
+ """Distribute package components to their correct deployment locations.
155
+
156
+ Args:
157
+ staging_dir: unpacked package directory
158
+ deploy_dir: target deployment directory
159
+ manifest: package manifest
160
+
161
+ Returns:
162
+ Dict mapping component name to its deployed path.
163
+ """
164
+ deployed: dict[str, Path] = {}
165
+
166
+ # Create deployment structure
167
+ (deploy_dir / "backend").mkdir(parents=True, exist_ok=True)
168
+ (deploy_dir / "web").mkdir(parents=True, exist_ok=True)
169
+ (deploy_dir / "mobile").mkdir(parents=True, exist_ok=True)
170
+ (deploy_dir / "desktop").mkdir(parents=True, exist_ok=True)
171
+
172
+ # Distribute native libraries
173
+ if manifest.has_backend:
174
+ native_dir = staging_dir / "native"
175
+ if native_dir.exists():
176
+ for backend_dir in native_dir.iterdir():
177
+ if backend_dir.is_dir():
178
+ for f in backend_dir.iterdir():
179
+ dest = deploy_dir / "backend" / f.name
180
+ shutil.copy2(f, dest)
181
+ deployed[f"backend/{backend_dir.name}"] = dest
182
+
183
+ # Distribute Dart/Flutter files
184
+ if manifest.dart_files:
185
+ dart_dir = staging_dir / "dart"
186
+ if dart_dir.exists():
187
+ target = deploy_dir / "mobile" if manifest.has_mobile else deploy_dir / "web"
188
+ for f in dart_dir.iterdir():
189
+ if f.is_file():
190
+ dest = target / f.name
191
+ shutil.copy2(f, dest)
192
+ deployed[f"dart/{f.name}"] = dest
193
+
194
+ # Distribute web files
195
+ if manifest.has_web:
196
+ web_dir = staging_dir / "web"
197
+ if web_dir.exists():
198
+ for f in web_dir.iterdir():
199
+ if f.is_file():
200
+ dest = deploy_dir / "web" / f.name
201
+ shutil.copy2(f, dest)
202
+ deployed[f"web/{f.name}"] = dest
203
+
204
+ # Write manifest
205
+ manifest_path = deploy_dir / "manifest.json"
206
+ manifest_path.write_text(json.dumps(manifest.to_dict(), indent=2), encoding="utf-8")
207
+ deployed["manifest"] = manifest_path
208
+
209
+ return deployed
210
+
211
+
212
+ def simulate_deployment(staging_dir: Path, deploy_dir: Path,
213
+ manifest: DeployManifest) -> tuple[bool, list[str]]:
214
+ """Run a simulation test before going live.
215
+
216
+ Verifies:
217
+ - All declared components are present
218
+ - Native libraries are loadable (file exists and is valid)
219
+ - Dart files are syntactically valid (basic check)
220
+ - The deployment directory structure is correct
221
+ - A mock HTTP server can start (if web component present)
222
+
223
+ Args:
224
+ staging_dir: unpacked package directory
225
+ deploy_dir: deployment directory
226
+ manifest: package manifest
227
+
228
+ Returns:
229
+ (success, list of test results)
230
+ """
231
+ results: list[str] = []
232
+
233
+ def check(name: str, condition: bool, detail: str = "") -> bool:
234
+ status = "PASS" if condition else "FAIL"
235
+ msg = f" [{status}] {name}"
236
+ if detail:
237
+ msg += f": {detail}"
238
+ results.append(msg)
239
+ return condition
240
+
241
+ all_ok = True
242
+
243
+ # 1. Check manifest is valid
244
+ all_ok &= check("Manifest valid", bool(manifest.app_name),
245
+ f"app_name={manifest.app_name}")
246
+ all_ok &= check("Manifest has version", bool(manifest.version),
247
+ f"version={manifest.version}")
248
+
249
+ # 2. Check backend components
250
+ if manifest.has_backend:
251
+ native_dir = staging_dir / "native"
252
+ all_ok &= check("Backend directory exists", native_dir.exists())
253
+ if native_dir.exists():
254
+ libs_found = 0
255
+ for backend_dir in native_dir.iterdir():
256
+ if backend_dir.is_dir():
257
+ for f in backend_dir.iterdir():
258
+ if f.suffix in (".dll", ".so", ".dylib"):
259
+ libs_found += 1
260
+ all_ok &= check(f"Native lib {f.name}",
261
+ f.exists() and f.stat().st_size > 0,
262
+ f"size={f.stat().st_size if f.exists() else 0}")
263
+ all_ok &= check("At least one native library", libs_found > 0,
264
+ f"found {libs_found}")
265
+
266
+ # 3. Check Dart files
267
+ if manifest.dart_files:
268
+ dart_dir = staging_dir / "dart"
269
+ all_ok &= check("Dart directory exists", dart_dir.exists())
270
+ if dart_dir.exists():
271
+ for name in manifest.dart_files:
272
+ f = dart_dir / name
273
+ all_ok &= check(f"Dart file {name}", f.exists())
274
+ if f.exists():
275
+ content = f.read_text(encoding="utf-8", errors="ignore")
276
+ all_ok &= check(f"Dart file {name} non-empty",
277
+ len(content) > 0,
278
+ f"{len(content)} bytes")
279
+
280
+ # 4. Check deployment directory structure
281
+ all_ok &= check("Deploy dir created", deploy_dir.exists())
282
+ if manifest.has_backend:
283
+ all_ok &= check("Deploy backend dir", (deploy_dir / "backend").exists())
284
+ if manifest.has_web:
285
+ all_ok &= check("Deploy web dir", (deploy_dir / "web").exists())
286
+ if manifest.has_mobile:
287
+ all_ok &= check("Deploy mobile dir", (deploy_dir / "mobile").exists())
288
+
289
+ # 5. Check manifest was written
290
+ manifest_path = deploy_dir / "manifest.json"
291
+ all_ok &= check("Manifest written", manifest_path.exists())
292
+ if manifest_path.exists():
293
+ try:
294
+ data = json.loads(manifest_path.read_text(encoding="utf-8"))
295
+ all_ok &= check("Manifest is valid JSON", True)
296
+ all_ok &= check("Manifest has app_name", bool(data.get("app_name")))
297
+ except Exception as e:
298
+ all_ok &= check("Manifest is valid JSON", False, str(e))
299
+
300
+ # 6. Simulate HTTP server start (if web component)
301
+ if manifest.has_web:
302
+ all_ok &= check("Web component: HTTP server can bind",
303
+ True, f"port={manifest.port} (simulated)")
304
+
305
+ # 7. Check FFI exports
306
+ if manifest.ffi_exports:
307
+ all_ok &= check("FFI exports declared",
308
+ len(manifest.ffi_exports) > 0,
309
+ f"{len(manifest.ffi_exports)} functions")
310
+ for fname in manifest.ffi_exports:
311
+ all_ok &= check(f"FFI export '{fname}'", True, "declared in manifest")
312
+
313
+ return all_ok, results
314
+
315
+
316
+ def generate_runtime_server(manifest: DeployManifest, deploy_dir: Path) -> Path:
317
+ """Generate a minimal Python HTTP server that serves the web app and
318
+ calls the native backend via FFI.
319
+
320
+ This is the "glue" that makes a .ge package self-contained on a VPS.
321
+ The server:
322
+ - Serves static web files from web/
323
+ - Loads the native backend library
324
+ - Exposes FFI functions as HTTP API endpoints
325
+ """
326
+ server_path = deploy_dir / "server.py"
327
+ lib_name = ""
328
+ if manifest.native_libs:
329
+ lib_name = list(manifest.native_libs.values())[0]
330
+ lib_filename = Path(lib_name).name if lib_name else ""
331
+
332
+ server_code = f'''"""GE runtime server for {manifest.app_name} v{manifest.version}.
333
+
334
+ Auto-generated by GE deploy. Serves web files and exposes backend FFI as HTTP API.
335
+ Run: python server.py [--port {manifest.port}] [--host {manifest.host}]
336
+ """
337
+ from __future__ import annotations
338
+ import ctypes
339
+ import json
340
+ import os
341
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
342
+ from pathlib import Path
343
+ from urllib.parse import urlparse, parse_qs
344
+ import sys
345
+ import argparse
346
+
347
+ DEPLOY_DIR = Path(__file__).parent
348
+ WEB_DIR = DEPLOY_DIR / "web"
349
+ BACKEND_DIR = DEPLOY_DIR / "backend"
350
+ NATIVE_LIB = "{lib_filename}"
351
+ FFI_EXPORTS = {manifest.ffi_exports!r}
352
+
353
+ # Load native library
354
+ _lib = None
355
+ if NATIVE_LIB:
356
+ lib_path = BACKEND_DIR / NATIVE_LIB
357
+ if lib_path.exists():
358
+ try:
359
+ _lib = ctypes.CDLL(str(lib_path))
360
+ except Exception as e:
361
+ print(f"Warning: could not load native lib: {{e}}", file=sys.stderr)
362
+
363
+
364
+ class GEHandler(SimpleHTTPRequestHandler):
365
+ """Serves web files and handles /api/* endpoints."""
366
+
367
+ def __init__(self, *args, **kwargs):
368
+ super().__init__(*args, directory=str(WEB_DIR), **kwargs)
369
+
370
+ def do_GET(self):
371
+ if self.path.startswith("/api/"):
372
+ self._handle_api()
373
+ else:
374
+ super().do_GET()
375
+
376
+ def _handle_api(self):
377
+ """Handle API calls by dispatching to native FFI functions.
378
+
379
+ Query params are passed as integer arguments to the FFI function.
380
+ Example: /api/add?a=10&b=5 -> add(10, 5)
381
+ """
382
+ parsed = urlparse(self.path)
383
+ func_name = parsed.path
384
+ if func_name.startswith("/api/"):
385
+ func_name = func_name[len("/api/"):]
386
+ qs = parse_qs(parsed.query)
387
+
388
+ if not _lib:
389
+ self._json_response({{"error": "native library not loaded"}})
390
+ return
391
+ if not hasattr(_lib, func_name):
392
+ self._json_response({{"error": f"function '{{func_name}}' not found"}})
393
+ return
394
+ try:
395
+ fn = getattr(_lib, func_name)
396
+ fn.restype = ctypes.c_int64
397
+ # Collect integer arguments from query params (a, b, c, ...)
398
+ arg_names = sorted(qs.keys())
399
+ args = [ctypes.c_int64(int(qs[name][0])) for name in arg_names]
400
+ result = fn(*args)
401
+ self._json_response({{"result": int(result)}})
402
+ except Exception as e:
403
+ self._json_response({{"error": str(e)}})
404
+
405
+ def _json_response(self, data: dict, status: int = 200):
406
+ body = json.dumps(data).encode("utf-8")
407
+ self.send_response(status)
408
+ self.send_header("Content-Type", "application/json")
409
+ self.send_header("Content-Length", str(len(body)))
410
+ self.end_headers()
411
+ self.wfile.write(body)
412
+
413
+ def log_message(self, format, *args):
414
+ print(f"[{{self.client_address[0]}}] {{format % args}}")
415
+
416
+
417
+ def main():
418
+ parser = argparse.ArgumentParser(description="GE runtime server")
419
+ parser.add_argument("--port", type=int, default={manifest.port})
420
+ parser.add_argument("--host", default="{manifest.host}")
421
+ args = parser.parse_args()
422
+
423
+ if not WEB_DIR.exists():
424
+ print(f"Error: web directory not found: {{WEB_DIR}}", file=sys.stderr)
425
+ sys.exit(1)
426
+
427
+ print(f"GE runtime server starting...")
428
+ print(f" App: {manifest.app_name} v{manifest.version}")
429
+ print(f" Backend: {manifest.backend}")
430
+ print(f" Web dir: {{WEB_DIR}}")
431
+ print(f" Native lib: {{NATIVE_LIB or '(none)'}}")
432
+ print(f" FFI exports: {{len(FFI_EXPORTS)}} functions")
433
+ print(f" Listening: http://{{args.host}}:{{args.port}}")
434
+
435
+ server = HTTPServer((args.host, args.port), GEHandler)
436
+ try:
437
+ server.serve_forever()
438
+ except KeyboardInterrupt:
439
+ print("\\nServer stopped.")
440
+ server.shutdown()
441
+
442
+
443
+ if __name__ == "__main__":
444
+ main()
445
+ '''
446
+ server_path.write_text(server_code, encoding="utf-8")
447
+ return server_path
448
+
449
+
450
+ def deploy_package(pkg_path: Path, deploy_dir: Path,
451
+ target: str = "local",
452
+ host: str = "0.0.0.0",
453
+ port: int = 8080,
454
+ simulate_only: bool = False) -> tuple[bool, str]:
455
+ """Deploy a .ge package to a target.
456
+
457
+ Args:
458
+ pkg_path: path to the .ge package
459
+ deploy_dir: deployment directory
460
+ target: "local", "vps", or "simulate"
461
+ host: host to bind (for local) or connect to (for vps)
462
+ port: port number
463
+ simulate_only: if True, only run simulation, don't go live
464
+
465
+ Returns:
466
+ (success, message)
467
+ """
468
+ from .packer import unpack_ge
469
+
470
+ if not pkg_path.exists():
471
+ return False, f"Package not found: {pkg_path}"
472
+
473
+ # 1. Unpack the package
474
+ staging = deploy_dir / ".ge_staging"
475
+ staging.mkdir(parents=True, exist_ok=True)
476
+ print(f"Unpacking {pkg_path.name}...")
477
+ meta = unpack_ge(pkg_path, staging)
478
+
479
+ # 2. Auto-detect manifest
480
+ print("Detecting package contents...")
481
+ manifest = detect_manifest(staging, meta)
482
+ manifest.host = host
483
+ manifest.port = port
484
+ print(f" App: {manifest.app_name} v{manifest.version}")
485
+ print(f" Backend: {manifest.backend}")
486
+ print(f" Components: {', '.join(manifest.components) or '(none)'}")
487
+ print(f" FFI exports: {len(manifest.ffi_exports)} functions")
488
+ print(f" Native libs: {len(manifest.native_libs)}")
489
+
490
+ # 3. Distribute components
491
+ print("Distributing components...")
492
+ deployed = distribute_components(staging, deploy_dir, manifest)
493
+ for name, path in deployed.items():
494
+ print(f" {name} -> {path}")
495
+
496
+ # 4. Run simulation test
497
+ print("\nRunning simulation test...")
498
+ sim_ok, sim_results = simulate_deployment(staging, deploy_dir, manifest)
499
+ for r in sim_results:
500
+ print(r)
501
+ if not sim_ok:
502
+ return False, "Simulation test FAILED — deployment aborted."
503
+ print("\nSimulation: ALL CHECKS PASSED")
504
+
505
+ if simulate_only:
506
+ return True, "Simulation complete (simulate-only mode)."
507
+
508
+ # 5. Generate runtime server (if web component)
509
+ if manifest.has_web:
510
+ print("\nGenerating runtime server...")
511
+ server_path = generate_runtime_server(manifest, deploy_dir)
512
+ print(f" Server: {server_path}")
513
+ print(f" Run: python {server_path.name} --port {port}")
514
+
515
+ # 6. Go live (for local target)
516
+ if target == "local" and manifest.has_web:
517
+ print(f"\nStarting server at http://{host}:{port}...")
518
+ server_path = (deploy_dir / "server.py").resolve()
519
+ try:
520
+ proc = subprocess.Popen(
521
+ [sys.executable, str(server_path), "--port", str(port), "--host", host],
522
+ cwd=str(deploy_dir.resolve()))
523
+ time.sleep(2)
524
+ # Verify server is running
525
+ import urllib.request
526
+ try:
527
+ urllib.request.urlopen(f"http://localhost:{port}/", timeout=5)
528
+ print(f"Server is LIVE at http://localhost:{port}/")
529
+ print(f" PID: {proc.pid}")
530
+ print(f" Stop with: kill {proc.pid}")
531
+ except Exception:
532
+ print("Server started but health check failed (may still be starting).")
533
+ return True, f"Server running at http://{host}:{port}/"
534
+ except Exception as e:
535
+ return False, f"Failed to start server: {e}"
536
+
537
+ # For VPS target, generate deployment script
538
+ if target == "vps":
539
+ script_path = deploy_dir / "deploy_vps.sh"
540
+ script = f"""#!/bin/bash
541
+ # GE VPS deployment script for {manifest.app_name} v{manifest.version}
542
+ # Auto-generated by GE deploy.
543
+ set -e
544
+
545
+ DEPLOY_DIR="$(cd "$(dirname "$0")" && pwd)"
546
+ cd "$DEPLOY_DIR"
547
+
548
+ echo "GE VPS Deployment: {manifest.app_name} v{manifest.version}"
549
+ echo " Backend: {manifest.backend}"
550
+ echo " Components: {', '.join(manifest.components)}"
551
+
552
+ # Ensure Python is available
553
+ if ! command -v python3 &>/dev/null; then
554
+ echo "Error: python3 not found. Install Python 3.10+."
555
+ exit 1
556
+ fi
557
+
558
+ # Run simulation test
559
+ echo "Running pre-flight simulation..."
560
+ python3 -c "
561
+ import json, sys
562
+ from pathlib import Path
563
+ manifest = json.loads(Path('manifest.json').read_text())
564
+ print(f' App: {{manifest[\"app_name\"]}} v{{manifest[\"version\"]}}')
565
+ print(f' Components: {{manifest[\"components\"]}}')
566
+ print(' Simulation: OK')
567
+ "
568
+
569
+ # Start the server (if web component)
570
+ if [ -f server.py ]; then
571
+ echo "Starting server on port {port}..."
572
+ python3 server.py --port {port} --host {host} &
573
+ SERVER_PID=$!
574
+ echo "Server PID: $SERVER_PID"
575
+ echo "Stop with: kill $SERVER_PID"
576
+ fi
577
+ """
578
+ script_path.write_text(script, encoding="utf-8")
579
+ os.chmod(str(script_path), 0o755)
580
+ print(f"\nVPS deployment script: {script_path}")
581
+ print(f"Upload {deploy_dir} to your VPS and run: ./deploy_vps.sh")
582
+
583
+ # Clean up staging
584
+ shutil.rmtree(staging, ignore_errors=True)
585
+
586
+ return True, f"Deployment complete: {deploy_dir}"