build(electron): app now builds and runs on macOS as a standalone app

This commit is contained in:
2025-08-10 15:09:23 -04:00
parent 7cfc70632b
commit 63bc3a198d
7 changed files with 139 additions and 14 deletions

91
electron/build-backend.js Normal file
View File

@@ -0,0 +1,91 @@
#!/usr/bin/env node
/**
* Build script to ensure PyInstaller backend is built before Electron packaging
* This script is called by electron-builder before packaging
*/
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
function runCommand(command, args, cwd) {
return new Promise((resolve, reject) => {
console.log(`Running: ${command} ${args.join(' ')} in ${cwd}`);
const process = spawn(command, args, {
cwd,
stdio: 'inherit',
shell: true
});
process.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Command failed with exit code ${code}`));
}
});
process.on('error', (err) => {
reject(err);
});
});
}
async function buildBackend() {
const projectRoot = path.resolve(__dirname, '..');
const buildDir = path.join(projectRoot, 'build');
const backendDistPath = path.join(buildDir, 'electron', 'dist', 'fourdst-backend');
console.log('Building PyInstaller backend...');
console.log(`Project root: ${projectRoot}`);
console.log(`Build directory: ${buildDir}`);
console.log(`Target platform: ${process.platform}`);
try {
// Check if meson build directory exists
if (!fs.existsSync(buildDir)) {
console.log('Meson build directory not found. Setting up build...');
await runCommand('meson', ['setup', 'build', '--buildtype=release', '-Dbuild-py-backend=true'], projectRoot);
} else {
// Ensure py-backend option is enabled
console.log('Reconfiguring meson build with py-backend enabled...');
await runCommand('meson', ['configure', 'build', '-Dbuild-py-backend=true'], projectRoot);
}
// Build the backend using meson
console.log('Building backend with meson...');
await runCommand('meson', ['compile', '-C', 'build'], projectRoot);
// Verify the backend executable was created
const executableName = process.platform === 'win32' ? 'fourdst-backend.exe' : 'fourdst-backend';
const backendExecutable = path.join(backendDistPath, executableName);
if (fs.existsSync(backendExecutable)) {
console.log(`✅ Backend executable built successfully: ${backendExecutable}`);
// Make executable on Unix systems
if (process.platform !== 'win32') {
const { execSync } = require('child_process');
execSync(`chmod +x "${backendExecutable}"`);
console.log('✅ Backend executable permissions set');
}
} else {
throw new Error(`Backend executable not found at: ${backendExecutable}`);
}
} catch (error) {
console.error('❌ Failed to build backend:', error.message);
process.exit(1);
}
}
// Run the build if this script is called directly
if (require.main === module) {
buildBackend().catch(error => {
console.error('Build failed:', error);
process.exit(1);
});
}
module.exports = { buildBackend };

View File

@@ -1,6 +1,7 @@
# -*- mode: python ; coding: utf-8 -*-
import sys
import os
from pathlib import Path
# This is a PyInstaller spec file. It is used to bundle the Python backend
@@ -14,6 +15,15 @@ project_root = Path(SPECPATH).parent
# We need to add the project root to the path so that PyInstaller can find the 'fourdst' module.
sys.path.insert(0, str(project_root))
# Platform-specific configurations
platform = sys.platform
print(f"Building for platform: {platform}")
# Determine executable name based on platform
exe_name = 'fourdst-backend'
if platform == 'win32':
exe_name += '.exe'
# The main script to be bundled.
analysis = Analysis(['bridge.py'],
pathex=[str(project_root)],
@@ -35,7 +45,7 @@ exe = EXE(pyz,
analysis.scripts,
[],
exclude_binaries=True,
name='fourdst-backend',
name=exe_name,
debug=False,
bootloader_ignore_signals=False,
strip=False,

View File

@@ -2,8 +2,13 @@ const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron');
const path = require('path');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
try {
if (require('electron-squirrel-startup')) {
app.quit();
}
} catch (error) {
// electron-squirrel-startup is not available or not needed on this platform
console.log('electron-squirrel-startup not available, continuing...');
}
let mainWindow;

View File

@@ -6,10 +6,16 @@ const { spawn } = require('child_process');
function runPythonCommand(command, kwargs, event) {
const buildDir = path.resolve(__dirname, '..', '..', 'build');
let backendPath;
// Determine executable name based on platform
const executableName = process.platform === 'win32' ? 'fourdst-backend.exe' : 'fourdst-backend';
if (app.isPackaged) {
backendPath = path.join(process.resourcesPath, 'fourdst-backend');
// In packaged app, backend is in resources/backend/ directory
backendPath = path.join(process.resourcesPath, 'backend', executableName);
} else {
backendPath = path.join(buildDir, 'electron', 'dist', 'fourdst-backend', 'fourdst-backend');
// In development, use the meson build output
backendPath = path.join(buildDir, 'electron', 'dist', 'fourdst-backend', executableName);
}
console.log(`[MAIN_PROCESS] Spawning backend: ${backendPath}`);

View File

@@ -11,6 +11,7 @@
"dependencies": {
"@electron/remote": "^2.0.0",
"adm-zip": "^0.5.14",
"electron-squirrel-startup": "^1.0.1",
"fs-extra": "^11.0.0",
"js-yaml": "^4.1.0",
"plotly.js-dist": "^2.26.0",
@@ -19,8 +20,7 @@
"devDependencies": {
"adm-zip": "^0.5.14",
"electron": "^31.0.2",
"electron-builder": "^24.0.0",
"electron-squirrel-startup": "^1.0.1"
"electron-builder": "^24.0.0"
}
},
"node_modules/@develar/schema-utils": {
@@ -1800,7 +1800,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/electron-squirrel-startup/-/electron-squirrel-startup-1.0.1.tgz",
"integrity": "sha512-sTfFIHGku+7PsHLJ7v0dRcZNkALrV+YEozINTW8X1nM//e5O3L+rfYuvSW00lmGHnYmUjARZulD8F2V8ISI9RA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"debug": "^2.2.0"
@@ -1810,7 +1809,6 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
@@ -1820,7 +1818,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true,
"license": "MIT"
},
"node_modules/emoji-regex": {

View File

@@ -6,7 +6,9 @@
"scripts": {
"start": "electron .",
"dev": "electron .",
"prebuild": "node build-backend.js",
"build": "electron-builder",
"prepack": "node build-backend.js",
"pack": "electron-builder --dir"
},
"repository": {
@@ -23,8 +25,7 @@
"devDependencies": {
"electron": "^31.0.2",
"adm-zip": "^0.5.14",
"electron-builder": "^24.0.0",
"electron-squirrel-startup": "^1.0.1"
"electron-builder": "^24.0.0"
},
"dependencies": {
"fs-extra": "^11.0.0",
@@ -32,7 +33,8 @@
"adm-zip": "^0.5.14",
"@electron/remote": "^2.0.0",
"python-shell": "^5.0.0",
"plotly.js-dist": "^2.26.0"
"plotly.js-dist": "^2.26.0",
"electron-squirrel-startup": "^1.0.1"
},
"build": {
"appId": "com.fourdst.bundlemanager",
@@ -40,6 +42,20 @@
"directories": {
"output": "dist"
},
"icon": "toolkitIcon.png",
"files": [
"**/*",
"node_modules/**/*",
"!node_modules/electron/**/*",
"!node_modules/electron-builder/**/*"
],
"extraResources": [
{
"from": "../build/electron/dist/fourdst-backend/",
"to": "backend/",
"filter": ["**/*"]
}
],
"mac": {
"category": "public.app-category.developer-tools",
"target": [

View File

@@ -504,7 +504,7 @@ body.dark-mode .info-button:hover {
flex-direction: column;
overflow: hidden;
min-width: 0;
padding: 20px;
padding: 0;
}
#welcome-screen {