add desktop build support
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,3 +1,9 @@
|
||||
*__pycache__*
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
pytest-cache-files-*/
|
||||
desktop-build/
|
||||
desktop-dist/
|
||||
frontend/node_modules/
|
||||
frontend/dist/
|
||||
frontend/dist/
|
||||
.venv/
|
||||
204
README.md
Normal file
204
README.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Argonode
|
||||
|
||||
Argonode is a node-based image analysis application with:
|
||||
|
||||
- a Python backend built on `aiohttp`
|
||||
- a React + Vite frontend
|
||||
- an optional desktop wrapper built with `pywebview`
|
||||
|
||||
The backend serves node definitions, runs workflows, manages file I/O, and streams previews/results over WebSocket. The frontend provides the graph editor and UI. The desktop build packages both together as a Windows application.
|
||||
|
||||
## Project Layout
|
||||
|
||||
```text
|
||||
argonode/
|
||||
backend/ Python server, execution engine, nodes
|
||||
frontend/ React/Vite app
|
||||
tests/ Python tests
|
||||
desktop.py Local desktop launcher
|
||||
scripts/ Build helpers, including Windows exe packaging
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python `3.10+`
|
||||
- Node.js `18+`
|
||||
- npm `9+`
|
||||
- Windows is recommended for the desktop `.exe` packaging flow
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
Create a virtual environment if you do not already have one:
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
```
|
||||
|
||||
Install Python dependencies:
|
||||
|
||||
```powershell
|
||||
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Install Node dependencies from the repo root:
|
||||
|
||||
```powershell
|
||||
npm install
|
||||
```
|
||||
|
||||
Optional extras:
|
||||
|
||||
```powershell
|
||||
.\.venv\Scripts\python.exe -m pip install -e .[dev]
|
||||
.\.venv\Scripts\python.exe -m pip install -e .[spm]
|
||||
.\.venv\Scripts\python.exe -m pip install -e .[desktop]
|
||||
```
|
||||
|
||||
- `dev`: test tooling
|
||||
- `spm`: optional SPM/AFM file readers like `gwyfile`, `nanonispy`, and `igor`
|
||||
- `desktop`: desktop launcher and PyInstaller packaging tools
|
||||
|
||||
## Running the Local Web Version
|
||||
|
||||
This is the normal browser-based development flow.
|
||||
|
||||
In terminal 1, start the backend:
|
||||
|
||||
```powershell
|
||||
npm run backend
|
||||
```
|
||||
|
||||
This starts the Python server at `http://127.0.0.1:8188`.
|
||||
|
||||
In terminal 2, start the Vite frontend:
|
||||
|
||||
```powershell
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open the Vite URL shown in the terminal, typically:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:5173
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- The frontend dev server proxies API and WebSocket requests to the backend.
|
||||
- If you want the frontend accessible from other devices on your LAN, run:
|
||||
|
||||
```powershell
|
||||
npm run dev -- --host 0.0.0.0
|
||||
```
|
||||
|
||||
## Running the Local Desktop Version
|
||||
|
||||
The desktop launcher starts the Python server internally and opens a native window with `pywebview`.
|
||||
|
||||
Build the frontend first:
|
||||
|
||||
```powershell
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then launch the desktop app from source:
|
||||
|
||||
```powershell
|
||||
npm run desktop
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `npm run desktop` uses the built frontend from `frontend/dist`.
|
||||
- If you change frontend code, run `npm run build` again before starting the desktop version.
|
||||
|
||||
## Building the Windows `.exe`
|
||||
|
||||
The repo includes a packaging script that:
|
||||
|
||||
1. builds the frontend
|
||||
2. installs desktop build dependencies
|
||||
3. runs PyInstaller
|
||||
|
||||
Build the desktop bundle:
|
||||
|
||||
```powershell
|
||||
npm run build:desktop
|
||||
```
|
||||
|
||||
Or run the script directly:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File scripts\build-desktop.ps1
|
||||
```
|
||||
|
||||
The packaged app is written to:
|
||||
|
||||
```text
|
||||
desktop-dist/Argonode/
|
||||
```
|
||||
|
||||
Main executable:
|
||||
|
||||
```text
|
||||
desktop-dist/Argonode/Argonode.exe
|
||||
```
|
||||
|
||||
### One-File Build
|
||||
|
||||
The default build uses PyInstaller `--onedir`, which is more reliable for scientific Python packages like NumPy, SciPy, and Matplotlib.
|
||||
|
||||
If you still want to try a single-file executable:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File scripts\build-desktop.ps1 -OneFile
|
||||
```
|
||||
|
||||
## Data Directories
|
||||
|
||||
During normal source-based development, input/output folders live under the repo root.
|
||||
|
||||
In the packaged desktop app, writable data is stored under:
|
||||
|
||||
```text
|
||||
%LOCALAPPDATA%\Argonode\
|
||||
```
|
||||
|
||||
Specifically:
|
||||
|
||||
```text
|
||||
%LOCALAPPDATA%\Argonode\input
|
||||
%LOCALAPPDATA%\Argonode\output
|
||||
```
|
||||
|
||||
You can override the packaged app data directory with:
|
||||
|
||||
```powershell
|
||||
$env:ARGONODE_APPDATA="C:\path\to\custom\data"
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```powershell
|
||||
npm run dev
|
||||
npm run build
|
||||
npm run preview
|
||||
npm run backend
|
||||
npm run desktop
|
||||
npm run build:desktop
|
||||
.\.venv\Scripts\python.exe -m pytest -q
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the Python test suite with:
|
||||
|
||||
```powershell
|
||||
.\.venv\Scripts\python.exe -m pytest -q
|
||||
```
|
||||
|
||||
## Known Notes
|
||||
|
||||
- The frontend production build currently emits a large chunk warning from Vite. This does not block builds.
|
||||
- The desktop app relies on WebView2 on Windows through `pywebview`.
|
||||
- Optional SPM readers are not installed unless you explicitly install the `spm` extra.
|
||||
@@ -9,10 +9,11 @@ from pathlib import Path
|
||||
|
||||
from backend.node_registry import register_node
|
||||
from backend.data_types import DataField, encode_preview, image_to_uint8
|
||||
from backend.runtime_paths import input_dir, output_dir
|
||||
|
||||
# Resolved at server startup so nodes know where to look
|
||||
INPUT_DIR = Path(__file__).parent.parent.parent / "input"
|
||||
OUTPUT_DIR = Path(__file__).parent.parent.parent / "output"
|
||||
INPUT_DIR = input_dir()
|
||||
OUTPUT_DIR = output_dir()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
54
backend/runtime_paths.py
Normal file
54
backend/runtime_paths.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
APP_NAME = "Argonode"
|
||||
|
||||
|
||||
def project_root() -> Path:
|
||||
return Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def resource_root() -> Path:
|
||||
if getattr(sys, "frozen", False):
|
||||
return Path(getattr(sys, "_MEIPASS", Path(sys.executable).resolve().parent))
|
||||
return project_root()
|
||||
|
||||
|
||||
def frontend_dir() -> Path:
|
||||
bundled = resource_root() / "frontend"
|
||||
if bundled.exists():
|
||||
return bundled
|
||||
return project_root() / "frontend"
|
||||
|
||||
|
||||
def frontend_dist_dir() -> Path:
|
||||
return frontend_dir() / "dist"
|
||||
|
||||
|
||||
def app_data_dir() -> Path:
|
||||
override = os.getenv("ARGONODE_APPDATA")
|
||||
if override:
|
||||
return Path(override).expanduser().resolve()
|
||||
|
||||
if getattr(sys, "frozen", False):
|
||||
local_appdata = os.getenv("LOCALAPPDATA")
|
||||
base_dir = Path(local_appdata) if local_appdata else Path.home() / "AppData" / "Local"
|
||||
return (base_dir / APP_NAME).resolve()
|
||||
|
||||
return project_root()
|
||||
|
||||
|
||||
def input_dir() -> Path:
|
||||
return app_data_dir() / "input"
|
||||
|
||||
|
||||
def output_dir() -> Path:
|
||||
return app_data_dir() / "output"
|
||||
|
||||
|
||||
def ensure_runtime_dirs() -> None:
|
||||
input_dir().mkdir(parents=True, exist_ok=True)
|
||||
output_dir().mkdir(parents=True, exist_ok=True)
|
||||
@@ -24,17 +24,23 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from aiohttp import web, WSMsgType
|
||||
from backend.runtime_paths import (
|
||||
ensure_runtime_dirs,
|
||||
frontend_dir,
|
||||
frontend_dist_dir,
|
||||
input_dir,
|
||||
output_dir,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
FRONTEND_DIR = Path(__file__).parent.parent / "frontend"
|
||||
DIST_DIR = FRONTEND_DIR / "dist"
|
||||
INPUT_DIR = Path(__file__).parent.parent / "input"
|
||||
OUTPUT_DIR = Path(__file__).parent.parent / "output"
|
||||
FRONTEND_DIR = frontend_dir()
|
||||
DIST_DIR = frontend_dist_dir()
|
||||
INPUT_DIR = input_dir()
|
||||
OUTPUT_DIR = output_dir()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -67,8 +73,7 @@ def create_app(loop: asyncio.AbstractEventLoop) -> web.Application:
|
||||
from backend.node_registry import get_all_node_info
|
||||
from backend.execution import ExecutionEngine, new_prompt_id
|
||||
|
||||
INPUT_DIR.mkdir(exist_ok=True)
|
||||
OUTPUT_DIR.mkdir(exist_ok=True)
|
||||
ensure_runtime_dirs()
|
||||
|
||||
engine = ExecutionEngine()
|
||||
websockets: set[web.WebSocketResponse] = set()
|
||||
@@ -104,7 +109,11 @@ def create_app(loop: asyncio.AbstractEventLoop) -> web.Application:
|
||||
# Serve Vite build output if available, else raw frontend
|
||||
if (DIST_DIR / "index.html").exists():
|
||||
return web.FileResponse(DIST_DIR / "index.html")
|
||||
return web.FileResponse(FRONTEND_DIR / "index.html")
|
||||
if (FRONTEND_DIR / "index.html").exists():
|
||||
return web.FileResponse(FRONTEND_DIR / "index.html")
|
||||
raise web.HTTPInternalServerError(
|
||||
reason="Frontend build not found. Run `npm run build` before launching the packaged app."
|
||||
)
|
||||
|
||||
async def get_nodes(request: web.Request) -> web.Response:
|
||||
info = get_all_node_info()
|
||||
@@ -244,9 +253,10 @@ def create_app(loop: asyncio.AbstractEventLoop) -> web.Application:
|
||||
app.router.add_get("/ws", websocket_handler)
|
||||
|
||||
# Serve frontend static files (Vite build or raw)
|
||||
if DIST_DIR.exists():
|
||||
if (DIST_DIR / "assets").exists():
|
||||
app.router.add_static("/assets", DIST_DIR / "assets")
|
||||
app.router.add_static("/static", FRONTEND_DIR)
|
||||
if FRONTEND_DIR.exists():
|
||||
app.router.add_static("/static", FRONTEND_DIR)
|
||||
|
||||
# CORS — allow any origin (local dev only)
|
||||
async def _cors_middleware(app_, handler):
|
||||
|
||||
111
desktop.py
Normal file
111
desktop.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
import webview
|
||||
from aiohttp import web
|
||||
|
||||
from backend.runtime_paths import app_data_dir, ensure_runtime_dirs
|
||||
from backend.server import create_app
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
WINDOW_TITLE = "Argonode"
|
||||
|
||||
|
||||
def _pick_free_port() -> int:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.bind((HOST, 0))
|
||||
return int(sock.getsockname()[1])
|
||||
|
||||
|
||||
def _wait_for_server(url: str, timeout: float = 15.0) -> None:
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=0.5):
|
||||
return
|
||||
except Exception:
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError(f"Timed out waiting for server at {url}")
|
||||
|
||||
|
||||
def _run_server(host: str, port: int, ready: threading.Event, state: dict[str, object]) -> None:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
state["loop"] = loop
|
||||
|
||||
async def start() -> None:
|
||||
app = create_app(loop)
|
||||
runner = web.AppRunner(app, access_log=None)
|
||||
await runner.setup()
|
||||
site = web.TCPSite(runner, host, port)
|
||||
await site.start()
|
||||
state["runner"] = runner
|
||||
ready.set()
|
||||
|
||||
try:
|
||||
loop.run_until_complete(start())
|
||||
loop.run_forever()
|
||||
except Exception as exc:
|
||||
state["error"] = exc
|
||||
ready.set()
|
||||
raise
|
||||
finally:
|
||||
runner = state.get("runner")
|
||||
if isinstance(runner, web.AppRunner):
|
||||
loop.run_until_complete(runner.cleanup())
|
||||
loop.close()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)-8s %(name)s - %(message)s",
|
||||
)
|
||||
|
||||
ensure_runtime_dirs()
|
||||
|
||||
port = _pick_free_port()
|
||||
base_url = f"http://{HOST}:{port}"
|
||||
ready = threading.Event()
|
||||
state: dict[str, object] = {}
|
||||
|
||||
server_thread = threading.Thread(
|
||||
target=_run_server,
|
||||
args=(HOST, port, ready, state),
|
||||
name="argonode-server",
|
||||
daemon=True,
|
||||
)
|
||||
server_thread.start()
|
||||
ready.wait(timeout=15.0)
|
||||
|
||||
if "error" in state:
|
||||
raise RuntimeError("Argonode server failed to start") from state["error"]
|
||||
|
||||
_wait_for_server(f"{base_url}/nodes")
|
||||
|
||||
window = webview.create_window(
|
||||
WINDOW_TITLE,
|
||||
base_url,
|
||||
width=1600,
|
||||
height=1000,
|
||||
min_size=(1100, 720),
|
||||
)
|
||||
|
||||
def _shutdown() -> None:
|
||||
loop = state.get("loop")
|
||||
if isinstance(loop, asyncio.AbstractEventLoop):
|
||||
loop.call_soon_threadsafe(loop.stop)
|
||||
|
||||
window.events.closed += _shutdown
|
||||
logging.getLogger(__name__).info("Using app data directory: %s", app_data_dir())
|
||||
webview.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
43
frontend/package-lock.json
generated
43
frontend/package-lock.json
generated
@@ -14,6 +14,10 @@
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"vite": "^5.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -838,9 +842,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -855,9 +856,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -872,9 +870,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -889,9 +884,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -906,9 +898,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -923,9 +912,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -940,9 +926,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -957,9 +940,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -974,9 +954,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -991,9 +968,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1008,9 +982,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1025,9 +996,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1042,9 +1010,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
"name": "argonode-frontend",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
|
||||
15
package-lock.json
generated
Normal file
15
package-lock.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "argonode",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "argonode",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
package.json
Normal file
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "argonode",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "npm --prefix frontend install",
|
||||
"dev": "npm --prefix frontend run dev",
|
||||
"build": "npm --prefix frontend run build",
|
||||
"preview": "npm --prefix frontend run preview",
|
||||
"backend": "python -m backend.main",
|
||||
"desktop": "python desktop.py",
|
||||
"build:desktop": "powershell -ExecutionPolicy Bypass -File scripts\\build-desktop.ps1"
|
||||
}
|
||||
}
|
||||
35
pyproject.toml
Normal file
35
pyproject.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=68", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "argonode"
|
||||
version = "0.1.0"
|
||||
description = "Node-based image analysis app with a Python backend and React frontend."
|
||||
readme = "GWYDDION_FEATURE_GAP.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"aiohttp>=3.9,<4",
|
||||
"matplotlib>=3.8,<4",
|
||||
"numpy>=1.26,<3",
|
||||
"pillow>=10,<12",
|
||||
"scikit-image>=0.22,<1",
|
||||
"scipy>=1.12,<2",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
spm = [
|
||||
"gwyfile>=0.2",
|
||||
"igor>=0.3",
|
||||
"nanonispy>=1.1",
|
||||
]
|
||||
dev = [
|
||||
"pytest>=8,<9",
|
||||
]
|
||||
desktop = [
|
||||
"pyinstaller>=6,<7",
|
||||
"pywebview>=5,<6",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["backend*"]
|
||||
2
pytest.ini
Normal file
2
pytest.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
norecursedirs = .git .venv .pytest_cache frontend/node_modules frontend/dist pytest-cache-files-*
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# Core runtime dependencies are defined in pyproject.toml.
|
||||
# Install them from the repo root with:
|
||||
# python -m pip install -r requirements.txt
|
||||
-e .
|
||||
48
scripts/build-desktop.ps1
Normal file
48
scripts/build-desktop.ps1
Normal file
@@ -0,0 +1,48 @@
|
||||
param(
|
||||
[switch]$OneFile
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
Set-Location $repoRoot
|
||||
|
||||
$pythonExe = if (Test-Path ".\.venv\Scripts\python.exe") {
|
||||
".\.venv\Scripts\python.exe"
|
||||
} else {
|
||||
"python"
|
||||
}
|
||||
$frontendDist = Join-Path $repoRoot "frontend\dist"
|
||||
|
||||
Write-Host "Building frontend bundle..."
|
||||
npm run build
|
||||
|
||||
Write-Host "Installing desktop build dependencies..."
|
||||
& $pythonExe -m pip install -e ".[desktop]"
|
||||
|
||||
$mode = if ($OneFile) { "--onefile" } else { "--onedir" }
|
||||
|
||||
$pyInstallerArgs = @(
|
||||
"-m", "PyInstaller",
|
||||
"desktop.py",
|
||||
"--noconfirm",
|
||||
"--clean",
|
||||
"--name", "Argonode",
|
||||
"--windowed",
|
||||
$mode,
|
||||
"--distpath", "desktop-dist",
|
||||
"--workpath", "desktop-build",
|
||||
"--specpath", "desktop-build",
|
||||
"--add-data", "${frontendDist};frontend/dist",
|
||||
"--collect-all", "matplotlib",
|
||||
"--collect-all", "scipy",
|
||||
"--collect-all", "skimage",
|
||||
"--collect-all", "webview"
|
||||
)
|
||||
|
||||
Write-Host "Packaging desktop app..."
|
||||
& $pythonExe @pyInstallerArgs
|
||||
|
||||
Write-Host "Desktop build complete."
|
||||
Write-Host "Output folder: $repoRoot\desktop-dist\Argonode"
|
||||
Reference in New Issue
Block a user