@mytec: iter2.2 start
This commit is contained in:
@@ -1,25 +1,46 @@
|
|||||||
"""Entry point for PyInstaller bundle"""
|
"""Entry point for PyInstaller bundle"""
|
||||||
|
print("[RFCP] run_server.py starting...", flush=True)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Set base path for PyInstaller
|
# Set base path for PyInstaller
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
os.chdir(os.path.dirname(sys.executable))
|
base_dir = os.path.dirname(sys.executable)
|
||||||
# Fix uvicorn TTY detection — sys.stdin/stdout/stderr are None when frozen
|
os.chdir(base_dir)
|
||||||
|
print(f"[RFCP] Frozen mode, base dir: {base_dir}", flush=True)
|
||||||
|
|
||||||
|
# Fix uvicorn TTY detection — redirect None streams to a log file
|
||||||
|
log_path = os.path.join(base_dir, 'rfcp-server.log')
|
||||||
|
log_file = open(log_path, 'w')
|
||||||
|
if sys.stdout is None:
|
||||||
|
sys.stdout = log_file
|
||||||
|
if sys.stderr is None:
|
||||||
|
sys.stderr = log_file
|
||||||
if sys.stdin is None:
|
if sys.stdin is None:
|
||||||
sys.stdin = open(os.devnull, 'r')
|
sys.stdin = open(os.devnull, 'r')
|
||||||
if sys.stdout is None:
|
print(f"[RFCP] Log file: {log_path}", flush=True)
|
||||||
sys.stdout = open(os.devnull, 'w')
|
|
||||||
if sys.stderr is None:
|
|
||||||
sys.stderr = open(os.devnull, 'w')
|
|
||||||
|
|
||||||
|
print("[RFCP] Importing uvicorn...", flush=True)
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
|
print("[RFCP] Importing app.main...", flush=True)
|
||||||
|
try:
|
||||||
from app.main import app
|
from app.main import app
|
||||||
|
print("[RFCP] App imported successfully", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[RFCP] FATAL: Failed to import app: {e}", flush=True)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
host = os.environ.get('RFCP_HOST', '127.0.0.1')
|
host = os.environ.get('RFCP_HOST', '127.0.0.1')
|
||||||
port = int(os.environ.get('RFCP_PORT', '8888'))
|
port = int(os.environ.get('RFCP_PORT', '8888'))
|
||||||
|
|
||||||
|
print(f"[RFCP] Starting uvicorn on {host}:{port}", flush=True)
|
||||||
|
|
||||||
|
try:
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
app,
|
app,
|
||||||
host=host,
|
host=host,
|
||||||
@@ -27,3 +48,8 @@ if __name__ == '__main__':
|
|||||||
log_level='warning',
|
log_level='warning',
|
||||||
access_log=False,
|
access_log=False,
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[RFCP] FATAL: uvicorn.run failed: {e}", flush=True)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
178
desktop/main.js
178
desktop/main.js
@@ -8,36 +8,84 @@ const store = new Store();
|
|||||||
let mainWindow;
|
let mainWindow;
|
||||||
let splashWindow;
|
let splashWindow;
|
||||||
let backendProcess;
|
let backendProcess;
|
||||||
|
let backendLogStream;
|
||||||
|
|
||||||
|
// ── Paths ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Paths
|
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
const getResourcePath = (relativePath) => {
|
/**
|
||||||
|
* Get path to a bundled resource (extraResources).
|
||||||
|
* In production, extraResources live under process.resourcesPath.
|
||||||
|
* In dev, they live in the repo root.
|
||||||
|
*/
|
||||||
|
const getResourcePath = (...segments) => {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
return path.join(__dirname, '..', relativePath);
|
return path.join(__dirname, '..', ...segments);
|
||||||
}
|
}
|
||||||
return path.join(process.resourcesPath, relativePath);
|
return path.join(process.resourcesPath, ...segments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User data directory — writable, persists across updates.
|
||||||
|
* Windows: %APPDATA%\RFCP\data\
|
||||||
|
* Linux: ~/.config/RFCP/data/
|
||||||
|
* macOS: ~/Library/Application Support/RFCP/data/
|
||||||
|
*/
|
||||||
const getDataPath = () => {
|
const getDataPath = () => {
|
||||||
return path.join(app.getPath('userData'), 'data');
|
return path.join(app.getPath('userData'), 'data');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get backend executable name based on platform
|
/**
|
||||||
const getBackendExeName = () => {
|
* Log directory for backend output and crash logs.
|
||||||
switch (process.platform) {
|
* Windows: %APPDATA%\RFCP\logs\
|
||||||
case 'win32':
|
* Linux: ~/.config/RFCP/logs/
|
||||||
return 'rfcp-server.exe';
|
* macOS: ~/Library/Logs/RFCP/
|
||||||
case 'darwin':
|
*/
|
||||||
return 'rfcp-server';
|
const getLogPath = () => {
|
||||||
case 'linux':
|
return app.getPath('logs');
|
||||||
return 'rfcp-server';
|
|
||||||
default:
|
|
||||||
return 'rfcp-server';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure data directories exist
|
/** Backend executable path */
|
||||||
|
const getBackendExePath = () => {
|
||||||
|
const exeName = process.platform === 'win32' ? 'rfcp-server.exe' : 'rfcp-server';
|
||||||
|
if (isDev) {
|
||||||
|
return path.join(__dirname, '..', 'backend', exeName);
|
||||||
|
}
|
||||||
|
return getResourcePath('backend', exeName);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Frontend index.html path (production only) */
|
||||||
|
const getFrontendPath = () => {
|
||||||
|
return getResourcePath('frontend', 'index.html');
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Logging ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function log(msg) {
|
||||||
|
const line = `[${new Date().toISOString()}] ${msg}`;
|
||||||
|
console.log(line);
|
||||||
|
if (backendLogStream) {
|
||||||
|
backendLogStream.write(line + '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initLogFile() {
|
||||||
|
const logDir = getLogPath();
|
||||||
|
if (!fs.existsSync(logDir)) {
|
||||||
|
fs.mkdirSync(logDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const logFile = path.join(logDir, 'rfcp-main.log');
|
||||||
|
backendLogStream = fs.createWriteStream(logFile, { flags: 'w' });
|
||||||
|
log(`Log file: ${logFile}`);
|
||||||
|
log(`Platform: ${process.platform}, Electron: ${process.versions.electron}`);
|
||||||
|
log(`isDev: ${isDev}`);
|
||||||
|
log(`userData: ${app.getPath('userData')}`);
|
||||||
|
log(`resourcesPath: ${isDev ? '(dev mode)' : process.resourcesPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Data directories ───────────────────────────────────────────────
|
||||||
|
|
||||||
function ensureDataDirs() {
|
function ensureDataDirs() {
|
||||||
const dirs = ['terrain', 'osm', 'projects', 'cache'];
|
const dirs = ['terrain', 'osm', 'projects', 'cache'];
|
||||||
const dataPath = getDataPath();
|
const dataPath = getDataPath();
|
||||||
@@ -49,10 +97,12 @@ function ensureDataDirs() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
log(`Data path: ${dataPath}`);
|
||||||
return dataPath;
|
return dataPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create splash window
|
// ── Splash window ──────────────────────────────────────────────────
|
||||||
|
|
||||||
function createSplashWindow() {
|
function createSplashWindow() {
|
||||||
splashWindow = new BrowserWindow({
|
splashWindow = new BrowserWindow({
|
||||||
width: 400,
|
width: 400,
|
||||||
@@ -71,21 +121,29 @@ function createSplashWindow() {
|
|||||||
splashWindow.center();
|
splashWindow.center();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start Python backend
|
// ── Backend lifecycle ──────────────────────────────────────────────
|
||||||
|
|
||||||
async function startBackend() {
|
async function startBackend() {
|
||||||
const dataPath = ensureDataDirs();
|
const dataPath = ensureDataDirs();
|
||||||
|
const logDir = getLogPath();
|
||||||
|
|
||||||
|
// SQLite URL — normalize to forward slashes for cross-platform compatibility
|
||||||
|
const dbPath = path.join(dataPath, 'rfcp.db').replace(/\\/g, '/');
|
||||||
|
|
||||||
const env = {
|
const env = {
|
||||||
...process.env,
|
...process.env,
|
||||||
RFCP_DATA_PATH: dataPath,
|
RFCP_DATA_PATH: dataPath,
|
||||||
RFCP_DATABASE_URL: `sqlite:///${path.join(dataPath, 'rfcp.db')}`,
|
RFCP_DATABASE_URL: `sqlite:///${dbPath}`,
|
||||||
RFCP_HOST: '127.0.0.1',
|
RFCP_HOST: '127.0.0.1',
|
||||||
RFCP_PORT: '8888',
|
RFCP_PORT: '8888',
|
||||||
|
RFCP_LOG_PATH: logDir,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
// Development: run uvicorn
|
|
||||||
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
||||||
|
const backendCwd = path.join(__dirname, '..', 'backend');
|
||||||
|
log(`Starting dev backend: ${pythonCmd} -m uvicorn ... (cwd: ${backendCwd})`);
|
||||||
|
|
||||||
backendProcess = spawn(pythonCmd, [
|
backendProcess = spawn(pythonCmd, [
|
||||||
'-m', 'uvicorn',
|
'-m', 'uvicorn',
|
||||||
'app.main:app',
|
'app.main:app',
|
||||||
@@ -93,46 +151,63 @@ async function startBackend() {
|
|||||||
'--port', '8888',
|
'--port', '8888',
|
||||||
'--reload'
|
'--reload'
|
||||||
], {
|
], {
|
||||||
cwd: path.join(__dirname, '..', 'backend'),
|
cwd: backendCwd,
|
||||||
env,
|
env,
|
||||||
stdio: ['ignore', 'pipe', 'pipe']
|
stdio: ['ignore', 'pipe', 'pipe']
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Production: run PyInstaller bundle
|
const backendExe = getBackendExePath();
|
||||||
const backendExe = path.join(
|
const backendDir = path.dirname(backendExe);
|
||||||
getResourcePath('backend'),
|
log(`Starting production backend: ${backendExe}`);
|
||||||
getBackendExeName()
|
log(`Backend cwd: ${backendDir}`);
|
||||||
);
|
|
||||||
|
// Verify the exe exists
|
||||||
|
if (!fs.existsSync(backendExe)) {
|
||||||
|
log(`FATAL: Backend exe not found at ${backendExe}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Make executable on Unix
|
// Make executable on Unix
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
try {
|
try {
|
||||||
fs.chmodSync(backendExe, '755');
|
fs.chmodSync(backendExe, '755');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('chmod failed:', e);
|
log(`chmod warning: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
backendProcess = spawn(backendExe, [], {
|
backendProcess = spawn(backendExe, [], {
|
||||||
|
cwd: backendDir,
|
||||||
env,
|
env,
|
||||||
stdio: ['ignore', 'pipe', 'pipe']
|
stdio: ['ignore', 'pipe', 'pipe']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log backend output
|
// Pipe backend output to log
|
||||||
|
const backendLogFile = path.join(logDir, 'rfcp-backend.log');
|
||||||
|
const backendLog = fs.createWriteStream(backendLogFile, { flags: 'w' });
|
||||||
|
|
||||||
backendProcess.stdout?.on('data', (data) => {
|
backendProcess.stdout?.on('data', (data) => {
|
||||||
console.log(`[Backend] ${data}`);
|
const text = data.toString().trim();
|
||||||
|
log(`[Backend] ${text}`);
|
||||||
|
backendLog.write(text + '\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
backendProcess.stderr?.on('data', (data) => {
|
backendProcess.stderr?.on('data', (data) => {
|
||||||
console.error(`[Backend Error] ${data}`);
|
const text = data.toString().trim();
|
||||||
|
log(`[Backend:err] ${text}`);
|
||||||
|
backendLog.write(`[err] ${text}\n`);
|
||||||
});
|
});
|
||||||
|
|
||||||
backendProcess.on('error', (err) => {
|
backendProcess.on('error', (err) => {
|
||||||
console.error('Failed to start backend:', err);
|
log(`Failed to start backend: ${err.message}`);
|
||||||
dialog.showErrorBox('Backend Error', `Failed to start backend: ${err.message}`);
|
dialog.showErrorBox('Backend Error', `Failed to start backend: ${err.message}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
backendProcess.on('exit', (code, signal) => {
|
||||||
|
log(`Backend exited: code=${code}, signal=${signal}`);
|
||||||
|
});
|
||||||
|
|
||||||
// Wait for backend to be ready
|
// Wait for backend to be ready
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const maxAttempts = 60; // 30 seconds
|
const maxAttempts = 60; // 30 seconds
|
||||||
@@ -145,7 +220,7 @@ async function startBackend() {
|
|||||||
const response = await fetch('http://127.0.0.1:8888/api/health/');
|
const response = await fetch('http://127.0.0.1:8888/api/health/');
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
clearInterval(checkBackend);
|
clearInterval(checkBackend);
|
||||||
console.log('Backend ready!');
|
log(`Backend ready after ${attempts * 0.5}s`);
|
||||||
resolve(true);
|
resolve(true);
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
@@ -154,14 +229,15 @@ async function startBackend() {
|
|||||||
|
|
||||||
if (attempts >= maxAttempts) {
|
if (attempts >= maxAttempts) {
|
||||||
clearInterval(checkBackend);
|
clearInterval(checkBackend);
|
||||||
console.error('Backend failed to start in time');
|
log('Backend failed to start within 30s');
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create main window
|
// ── Main window ────────────────────────────────────────────────────
|
||||||
|
|
||||||
function createMainWindow() {
|
function createMainWindow() {
|
||||||
const windowState = store.get('windowState', {
|
const windowState = store.get('windowState', {
|
||||||
width: 1400,
|
width: 1400,
|
||||||
@@ -181,9 +257,6 @@ function createMainWindow() {
|
|||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
preload: path.join(__dirname, 'preload.js')
|
preload: path.join(__dirname, 'preload.js')
|
||||||
},
|
},
|
||||||
icon: path.join(__dirname, 'assets',
|
|
||||||
process.platform === 'win32' ? 'icon.ico' : 'icon.png'
|
|
||||||
),
|
|
||||||
title: 'RFCP - RF Coverage Planner',
|
title: 'RFCP - RF Coverage Planner',
|
||||||
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
|
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
|
||||||
});
|
});
|
||||||
@@ -196,10 +269,21 @@ function createMainWindow() {
|
|||||||
|
|
||||||
// Load frontend
|
// Load frontend
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
|
log('Loading frontend from dev server: http://localhost:5173');
|
||||||
mainWindow.loadURL('http://localhost:5173');
|
mainWindow.loadURL('http://localhost:5173');
|
||||||
mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadFile(path.join(getResourcePath('frontend'), 'index.html'));
|
const frontendIndex = getFrontendPath();
|
||||||
|
log(`Loading frontend from: ${frontendIndex}`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(frontendIndex)) {
|
||||||
|
log(`FATAL: Frontend not found at ${frontendIndex}`);
|
||||||
|
dialog.showErrorBox('Startup Error', `Frontend not found at:\n${frontendIndex}`);
|
||||||
|
app.quit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow.loadFile(frontendIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWindow.once('ready-to-show', () => {
|
mainWindow.once('ready-to-show', () => {
|
||||||
@@ -221,23 +305,26 @@ function createMainWindow() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// App lifecycle
|
// ── App lifecycle ──────────────────────────────────────────────────
|
||||||
|
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
|
initLogFile();
|
||||||
createSplashWindow();
|
createSplashWindow();
|
||||||
|
|
||||||
const backendStarted = await startBackend();
|
const backendStarted = await startBackend();
|
||||||
|
|
||||||
if (!backendStarted) {
|
if (!backendStarted) {
|
||||||
|
const logDir = getLogPath();
|
||||||
const result = await dialog.showMessageBox({
|
const result = await dialog.showMessageBox({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: 'Startup Error',
|
title: 'Startup Error',
|
||||||
message: 'Failed to start backend server.',
|
message: 'Failed to start backend server.',
|
||||||
detail: 'Please check logs and try again. Click "Show Logs" to open the log folder.',
|
detail: `Check logs at:\n${logDir}\n\nClick "Show Logs" to open the folder.`,
|
||||||
buttons: ['Quit', 'Show Logs']
|
buttons: ['Quit', 'Show Logs']
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.response === 1) {
|
if (result.response === 1) {
|
||||||
shell.openPath(app.getPath('logs'));
|
shell.openPath(logDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.quit();
|
app.quit();
|
||||||
@@ -268,10 +355,15 @@ app.on('before-quit', () => {
|
|||||||
if (backendProcess) {
|
if (backendProcess) {
|
||||||
backendProcess.kill();
|
backendProcess.kill();
|
||||||
}
|
}
|
||||||
|
if (backendLogStream) {
|
||||||
|
backendLogStream.end();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// IPC Handlers
|
// ── IPC Handlers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
ipcMain.handle('get-data-path', () => getDataPath());
|
ipcMain.handle('get-data-path', () => getDataPath());
|
||||||
|
ipcMain.handle('get-log-path', () => getLogPath());
|
||||||
ipcMain.handle('get-app-version', () => app.getVersion());
|
ipcMain.handle('get-app-version', () => app.getVersion());
|
||||||
ipcMain.handle('get-platform', () => process.platform);
|
ipcMain.handle('get-platform', () => process.platform);
|
||||||
|
|
||||||
|
|||||||
4419
desktop/package-lock.json
generated
Normal file
4419
desktop/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ const { contextBridge, ipcRenderer } = require('electron');
|
|||||||
contextBridge.exposeInMainWorld('rfcp', {
|
contextBridge.exposeInMainWorld('rfcp', {
|
||||||
// System info
|
// System info
|
||||||
getDataPath: () => ipcRenderer.invoke('get-data-path'),
|
getDataPath: () => ipcRenderer.invoke('get-data-path'),
|
||||||
|
getLogPath: () => ipcRenderer.invoke('get-log-path'),
|
||||||
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
||||||
getPlatform: () => ipcRenderer.invoke('get-platform'),
|
getPlatform: () => ipcRenderer.invoke('get-platform'),
|
||||||
getGpuInfo: () => ipcRenderer.invoke('get-gpu-info'),
|
getGpuInfo: () => ipcRenderer.invoke('get-gpu-info'),
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ python3 -m pip install -r requirements.txt
|
|||||||
python3 -m pip install pyinstaller
|
python3 -m pip install pyinstaller
|
||||||
cd ../installer
|
cd ../installer
|
||||||
python3 -m PyInstaller rfcp-server.spec --clean --noconfirm
|
python3 -m PyInstaller rfcp-server.spec --clean --noconfirm
|
||||||
mkdir -p ../desktop/backend-dist/darwin
|
mkdir -p ../desktop/backend-dist/mac
|
||||||
cp dist/rfcp-server ../desktop/backend-dist/darwin/
|
cp dist/rfcp-server ../desktop/backend-dist/mac/
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# 3. Create .icns icon if not exists
|
# 3. Create .icns icon if not exists
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ python -m pip install -r requirements.txt
|
|||||||
python -m pip install pyinstaller
|
python -m pip install pyinstaller
|
||||||
cd ../installer
|
cd ../installer
|
||||||
python -m PyInstaller rfcp-server.spec --clean --noconfirm
|
python -m PyInstaller rfcp-server.spec --clean --noconfirm
|
||||||
mkdir -p ../desktop/backend-dist/win32
|
mkdir -p ../desktop/backend-dist/win
|
||||||
cp dist/rfcp-server.exe ../desktop/backend-dist/win32/
|
cp dist/rfcp-server.exe ../desktop/backend-dist/win/
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# 3. Build Electron app
|
# 3. Build Electron app
|
||||||
|
|||||||
@@ -13,24 +13,76 @@ a = Analysis(
|
|||||||
(str(backend_path / 'app'), 'app'),
|
(str(backend_path / 'app'), 'app'),
|
||||||
],
|
],
|
||||||
hiddenimports=[
|
hiddenimports=[
|
||||||
|
# Uvicorn internals
|
||||||
'uvicorn.logging',
|
'uvicorn.logging',
|
||||||
|
'uvicorn.loops',
|
||||||
|
'uvicorn.loops.auto',
|
||||||
|
'uvicorn.loops.asyncio',
|
||||||
|
'uvicorn.protocols',
|
||||||
'uvicorn.protocols.http',
|
'uvicorn.protocols.http',
|
||||||
'uvicorn.protocols.http.auto',
|
'uvicorn.protocols.http.auto',
|
||||||
'uvicorn.protocols.http.h11_impl',
|
'uvicorn.protocols.http.h11_impl',
|
||||||
|
'uvicorn.protocols.http.httptools_impl',
|
||||||
'uvicorn.protocols.websockets',
|
'uvicorn.protocols.websockets',
|
||||||
'uvicorn.protocols.websockets.auto',
|
'uvicorn.protocols.websockets.auto',
|
||||||
|
'uvicorn.protocols.websockets.wsproto_impl',
|
||||||
'uvicorn.lifespan',
|
'uvicorn.lifespan',
|
||||||
'uvicorn.lifespan.on',
|
'uvicorn.lifespan.on',
|
||||||
'uvicorn.lifespan.off',
|
'uvicorn.lifespan.off',
|
||||||
|
# FastAPI / Starlette
|
||||||
|
'fastapi',
|
||||||
|
'fastapi.middleware',
|
||||||
|
'fastapi.middleware.cors',
|
||||||
|
'fastapi.routing',
|
||||||
|
'fastapi.responses',
|
||||||
|
'fastapi.exceptions',
|
||||||
|
'starlette',
|
||||||
|
'starlette.routing',
|
||||||
|
'starlette.middleware',
|
||||||
|
'starlette.middleware.cors',
|
||||||
|
'starlette.responses',
|
||||||
|
'starlette.requests',
|
||||||
|
'starlette.concurrency',
|
||||||
|
'starlette.formparsers',
|
||||||
|
'starlette.staticfiles',
|
||||||
|
# Pydantic
|
||||||
|
'pydantic',
|
||||||
|
'pydantic.fields',
|
||||||
|
'pydantic_settings',
|
||||||
|
'pydantic_core',
|
||||||
|
# HTTP / networking
|
||||||
'httpx',
|
'httpx',
|
||||||
|
'httpcore',
|
||||||
'h11',
|
'h11',
|
||||||
|
'httptools',
|
||||||
|
'anyio',
|
||||||
|
'anyio._backends',
|
||||||
|
'anyio._backends._asyncio',
|
||||||
|
'sniffio',
|
||||||
|
# MongoDB (motor/pymongo)
|
||||||
|
'motor',
|
||||||
|
'motor.motor_asyncio',
|
||||||
|
'pymongo',
|
||||||
|
'pymongo.errors',
|
||||||
|
'pymongo.collection',
|
||||||
|
'pymongo.database',
|
||||||
|
'pymongo.mongo_client',
|
||||||
|
# Async I/O
|
||||||
|
'aiofiles',
|
||||||
|
'aiofiles.os',
|
||||||
|
'aiofiles.ospath',
|
||||||
|
# Scientific
|
||||||
'numpy',
|
'numpy',
|
||||||
|
'numpy.core',
|
||||||
'scipy',
|
'scipy',
|
||||||
'scipy.special',
|
'scipy.special',
|
||||||
'scipy.interpolate',
|
'scipy.interpolate',
|
||||||
'aiosqlite',
|
# Multipart
|
||||||
'sqlalchemy',
|
'multipart',
|
||||||
'sqlalchemy.dialects.sqlite',
|
'python_multipart',
|
||||||
|
# Encoding
|
||||||
|
'email.mime',
|
||||||
|
'email.mime.multipart',
|
||||||
],
|
],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
hooksconfig={},
|
hooksconfig={},
|
||||||
@@ -58,7 +110,7 @@ exe = EXE(
|
|||||||
upx=True,
|
upx=True,
|
||||||
upx_exclude=[],
|
upx_exclude=[],
|
||||||
runtime_tmpdir=None,
|
runtime_tmpdir=None,
|
||||||
console=False, # No console window
|
console=True, # Show console for debugging — set to False for release
|
||||||
disable_windowed_traceback=False,
|
disable_windowed_traceback=False,
|
||||||
target_arch=None,
|
target_arch=None,
|
||||||
codesign_identity=None,
|
codesign_identity=None,
|
||||||
|
|||||||
Reference in New Issue
Block a user