Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/build_and_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Build and release Electron app

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Tag for the release (e.g. v1.0.0)'
required: false

jobs:
build-windows:
name: Build (Windows) and publish Release
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'

- name: Ensure execution policy (powershell)
run: |
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
shell: pwsh

- name: Install Python requirements
run: |
py -3 -m pip install --upgrade pip
py -3 -m pip install -r requirements.txt
shell: pwsh

- name: Build Electron distributable (includes backend exe)
run: |
pwsh -File .\build_electron.ps1
shell: pwsh

- name: Create GitHub Release and upload artifacts
uses: ncipollo/release-action@v1
with:
tag: ${{ github.event.inputs.tag || github.ref_name }}
files: electron_dist/**
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,87 @@ options:

## **🌞 More**

## Building a Windows standalone executable (optional)

You can build a single-file Windows executable using PyInstaller. The repository includes a helper PowerShell script `build_exe.ps1` to automate the build inside a temporary venv.

From the repository root (PowerShell):

```powershell
# Build (creates .\build_venv, installs PyInstaller, and builds .\dist\Zehef.exe)
.\build_exe.ps1

# Clean previous venv and build, then rebuild
.\build_exe.ps1 -Clean
```

After the build completes the executable will be at `./dist/Zehef.exe`. Run it from a command prompt:

```powershell
.\dist\Zehef.exe email@domain.com
```

Alternatively you can keep using the PowerShell launcher `run_zehef.ps1` (already included) which detects a Python 3 interpreter and runs `zehef.py` forwarding any arguments:

```powershell
.\run_zehef.ps1 email@domain.com
```

Notes:
- The build script creates a local virtual environment (`build_venv`) to avoid changing your global Python environment.
- You can edit `build_exe.ps1` to add an icon or other PyInstaller options.
- Creating an installer (Inno Setup) or a GUI wrapper can be added as a follow-up.
- Creating an installer (Inno Setup) can be added as a follow-up.

### Building the GUI executable

The repository includes a minimal Tkinter GUI at `zehef_gui.py`. To build a windowed GUI executable (no console) run the build script with the `-Target gui` flag:

```powershell
# Build GUI exe (creates .\build_venv and .\dist\ZehefGUI.exe)
.\build_exe.ps1 -Target gui

# Build GUI exe with a custom icon (relative to repo root)
.\build_exe.ps1 -Target gui -Icon assets\zehef_icon.ico
```

After the build completes you will have `./dist/ZehefGUI.exe` which can be launched by double-clicking or from a Start menu/installer.

If you prefer the console CLI exe instead, build with `-Target cli` (default):

```powershell
.\build_exe.ps1 -Target cli
```

## Electron desktop app (Windows exe)

You can wrap the FastAPI web UI in an Electron shell to produce a native desktop application (exe). The repository includes a scaffold under `web/electron/` and a helper script `build_electron.ps1`.

Prerequisites:
- Node.js + npm (for electron and electron-builder)
- Python 3 and the project dependencies (see `requirements.txt`)

Dev run (start backend + Electron window):

```powershell
# from repo root
cd web\electron
npm install
npm start
```

Build distributable (Windows EXE/installer):

```powershell
# from repo root
.\build_electron.ps1
```

Notes:
- `build_electron.ps1` runs `npm install` and then `electron-builder` (configured in `web/electron/package.json`). The produced artifacts are under `electron_dist`.
- The Electron main process starts the FastAPI backend (using your system Python) and opens the frontend in a window. Ensure Python + project deps are available on the machine that runs the packaged app.
- Packaging with electron-builder will include the Node side, but the Python backend is started from the system Python at runtime; for a fully standalone product you would need to bundle the Python runtime and your app (more advanced). I can help with that next if you want.


### **✔️ / ❌ Rules**

Expand Down
57 changes: 57 additions & 0 deletions build_electron.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
Param(
[string]$NodePath = 'node',
[string]$NpmPath = 'npm'
)

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
Set-Location $scriptDir

Write-Host "Preparing to build Electron app..." -ForegroundColor Cyan

if (-not (Get-Command $NpmPath -ErrorAction SilentlyContinue)) {
Write-Error "npm not found. Install Node.js (which includes npm) and re-run this script."
exit 1
}

$electronDir = Join-Path $scriptDir 'web\electron'
if (-not (Test-Path $electronDir)) {
Write-Error "Electron scaffold not found at $electronDir"
exit 1
}

# Build the Python backend exe first so it can be bundled into the Electron app
Write-Host "Building backend executable (PyInstaller) without console..." -ForegroundColor Cyan
& "$scriptDir\build_exe.ps1" -Target backend -NoConsole
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to build backend exe. Aborting Electron build."
exit $LASTEXITCODE
}

# Copy backend exe into electron resources so electron-builder can include it
$backendExe = Join-Path $scriptDir 'dist\ZehefBackend.exe'
if (-not (Test-Path $backendExe)) {
Write-Error "Expected backend exe not found at $backendExe"
exit 1
}

$destDir = Join-Path $electronDir 'backend'
if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir | Out-Null }
Copy-Item -Path $backendExe -Destination (Join-Path $destDir 'ZehefBackend.exe') -Force


Write-Host "Running npm install in $electronDir" -ForegroundColor Cyan
Push-Location $electronDir
& $NpmPath install
if ($LASTEXITCODE -ne 0) { Write-Error "npm install failed"; Pop-Location; exit $LASTEXITCODE }

Write-Host "Building Electron distributable (this may take a while)..." -ForegroundColor Cyan
& $NpmPath run dist
$rc = $LASTEXITCODE
Pop-Location

if ($rc -ne 0) {
Write-Error "Electron build failed (exit code $rc). Check the npm logs above for details."
exit $rc
}

Write-Host "Electron build finished. Output directory: electron_dist" -ForegroundColor Green
135 changes: 135 additions & 0 deletions build_exe.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<#
Build a standalone Windows executable for Zehef using PyInstaller.

Usage:
.\build_exe.ps1 # builds using py -3 or python
.\build_exe.ps1 -Clean # clean previous build artifacts then build

This script will:
- detect a Python 3 interpreter (py -3, python3, or python)
- create a temporary virtualenv in .\build_venv
- install/upgrade pip, setuptools, wheel, pyinstaller
- run PyInstaller to create a single-file exe
- copy the resulting exe to ./dist/Zehef.exe
#>

param(
[switch]$Clean,
[ValidateSet('cli','gui','backend')]
[string]$Target = 'cli',
[string]$Icon = '',
[switch]$NoConsole
)

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
Set-Location $scriptDir

function Find-Python3 {
if (Get-Command py -ErrorAction SilentlyContinue) {
& py -3 -c "import sys; assert sys.version_info[0]==3" > $null 2>&1
if ($LASTEXITCODE -eq 0) { return @{exe='py'; args='-3'} }
}
if (Get-Command python3 -ErrorAction SilentlyContinue) {
& python3 -c "import sys; assert sys.version_info[0]==3" > $null 2>&1
if ($LASTEXITCODE -eq 0) { return @{exe='python3'; args=''} }
}
if (Get-Command python -ErrorAction SilentlyContinue) {
& python -c "import sys; assert sys.version_info[0]==3" > $null 2>&1
if ($LASTEXITCODE -eq 0) { return @{exe='python'; args=''} }
}
return $null
}

$py = Find-Python3
if (-not $py) {
Write-Error "No Python 3 interpreter found. Install Python 3 and ensure 'py', 'python3', or 'python' is in PATH."
exit 1
}

$venvDir = Join-Path $scriptDir 'build_venv'
if ($Clean -and (Test-Path $venvDir)) {
Write-Host "Cleaning previous build venv..." -ForegroundColor Yellow
Remove-Item -Recurse -Force $venvDir
}

if (-not (Test-Path $venvDir)) {
Write-Host "Creating virtual environment in $venvDir" -ForegroundColor Cyan
if ($py.exe -eq 'py') {
& py -3 -m venv $venvDir
} else {
& $($py.exe) $($py.args) -m venv $venvDir
}
}

$pip = Join-Path $venvDir 'Scripts\pip.exe'
$python = Join-Path $venvDir 'Scripts\python.exe'

if (-not (Test-Path $pip)) {
Write-Error "Virtualenv creation failed or pip not found at $pip"
exit 1
}

Write-Host "Upgrading pip and installing PyInstaller in venv..." -ForegroundColor Cyan
& $pip install --upgrade pip setuptools wheel
& $pip install pyinstaller

Write-Host "Building executable with PyInstaller (target: $Target) ..." -ForegroundColor Cyan

# Remove previous PyInstaller artifacts if present
if (Test-Path "$scriptDir\build") { Remove-Item -Recurse -Force "$scriptDir\build" }
if (Test-Path "$scriptDir\dist") { Remove-Item -Recurse -Force "$scriptDir\dist" }
if (Test-Path "$scriptDir\zehef.spec") { Remove-Item -Force "$scriptDir\zehef.spec" }

if ($Target -eq 'gui') {
$entry = 'zehef_gui.py'
$name = 'ZehefGUI'
$windowed = '--noconsole'
} elseif ($Target -eq 'backend') {
# Build a packaged backend executable that runs the FastAPI app
$entry = 'web\backend\run_server.py'
$name = 'ZehefBackend'
# allow building backend without a console window when requested
if ($NoConsole) { $windowed = '--noconsole' } else { $windowed = '' }
} else {
$entry = 'zehef.py'
$name = 'Zehef'
$windowed = ''
}

$iconArg = ''
if ($Icon -ne '') {
$fullIcon = if ([System.IO.Path]::IsPathRooted($Icon)) { $Icon } else { Join-Path $scriptDir $Icon }
if (Test-Path $fullIcon) {
$iconArg = "--icon `"$fullIcon`""
} else {
Write-Warning "Icon not found at $fullIcon; building without icon."
}
}

$entryPath = Join-Path $scriptDir $entry
if (-not (Test-Path $entryPath)) {
Write-Error "Entry script not found: $entryPath"
exit 1
}

$pyinstallerArgs = @('--onefile', $windowed, '--name', $name)
if ($iconArg -ne '') { $pyinstallerArgs += $iconArg }
$pyinstallerArgs += $entry

& $python -m PyInstaller @pyinstallerArgs

if ($LASTEXITCODE -ne 0) {
Write-Error "PyInstaller failed (exit code $LASTEXITCODE)"
exit $LASTEXITCODE
}

$exePath = Join-Path $scriptDir ("dist\{0}.exe" -f $name)
if (Test-Path $exePath) {
Write-Host "Build succeeded: $exePath" -ForegroundColor Green
Write-Host "You can distribute the single file executable at: $exePath" -ForegroundColor Green
} else {
Write-Error "Expected exe not found at $exePath"
exit 1
}

Write-Host "Done." -ForegroundColor Cyan
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ httpx
requests
bs4
asyncio
fastapi
uvicorn[standard]
44 changes: 44 additions & 0 deletions run_zehef.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Param(
[Parameter(ValueFromRemainingArguments=$true)]
[string[]]$Args
)

# Run from script directory so relative paths work
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
Set-Location $scriptDir

# Detect a Python 3 interpreter: try python3, then py -3, then python
$found = $null

if (Get-Command python3 -ErrorAction SilentlyContinue) {
& python3 -c "import sys; assert sys.version_info[0]==3" > $null 2>&1
if ($LASTEXITCODE -eq 0) { $found = "python3" }
}

if (-not $found -and (Get-Command py -ErrorAction SilentlyContinue)) {
& py -3 -c "import sys; assert sys.version_info[0]==3" > $null 2>&1
if ($LASTEXITCODE -eq 0) { $found = "py -3" }
}

if (-not $found -and (Get-Command python -ErrorAction SilentlyContinue)) {
& python -c "import sys; assert sys.version_info[0]==3" > $null 2>&1
if ($LASTEXITCODE -eq 0) { $found = "python" }
}

if (-not $found) {
Write-Error "No Python 3 interpreter found. Install Python 3 and ensure 'python', 'python3', or the 'py' launcher is in PATH."
exit 1
}

if ($found -eq "py -3") {
$exe = "py"
$exeArgs = @("-3","zehef.py") + $Args
} else {
$exe = $found
$exeArgs = @("zehef.py") + $Args
}

Write-Host "Running: $exe $($exeArgs -join ' ')" -ForegroundColor Cyan
& $exe @exeArgs
$exit = $LASTEXITCODE
exit $exit
Loading