Browse Source

feat(electron): improve electron build configuration

- Add dedicated electron build scripts for each platform
- Configure Vite rollup options for electron builds
- Update electron-builder configuration with proper app metadata
- Add clean script and rimraf dependency
- Set proper appId and build settings for production builds
- Enable asar packaging for better security
pull/126/head
Matthew Raymer 1 week ago
parent
commit
562b27851c
  1. 152
      package-lock.json
  2. 38
      package.json
  3. 72
      scripts/build-electron.js
  4. 69
      src/electron/main.js
  5. 16
      vite.config.mjs

152
package-lock.json

@ -100,6 +100,7 @@
"npm-check-updates": "^17.1.13", "npm-check-updates": "^17.1.13",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"rimraf": "^6.0.1",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "~5.2.2", "typescript": "~5.2.2",
"vite": "^5.2.0", "vite": "^5.2.0",
@ -2628,6 +2629,66 @@
"node": ">=18.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/@capacitor/cli/node_modules/glob": {
"version": "9.3.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
"integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"minimatch": "^8.0.2",
"minipass": "^4.2.4",
"path-scurry": "^1.6.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@capacitor/cli/node_modules/minimatch": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
"integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@capacitor/cli/node_modules/minipass": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
"integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
"license": "ISC",
"engines": {
"node": ">=8"
}
},
"node_modules/@capacitor/cli/node_modules/rimraf": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz",
"integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==",
"license": "ISC",
"dependencies": {
"glob": "^9.2.0"
},
"bin": {
"rimraf": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@capacitor/core": { "node_modules/@capacitor/core": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz", "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz",
@ -23165,63 +23226,106 @@
} }
}, },
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "4.4.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"glob": "^9.2.0" "glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
}, },
"bin": { "bin": {
"rimraf": "dist/cjs/src/bin.js" "rimraf": "dist/esm/bin.mjs"
}, },
"engines": { "engines": {
"node": ">=14" "node": "20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/rimraf/node_modules/glob": { "node_modules/rimraf/node_modules/glob": {
"version": "9.3.5", "version": "11.0.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz",
"integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"fs.realpath": "^1.0.0", "foreground-child": "^3.1.0",
"minimatch": "^8.0.2", "jackspeak": "^4.0.1",
"minipass": "^4.2.4", "minimatch": "^10.0.0",
"path-scurry": "^1.6.1" "minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/jackspeak": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.3.tgz",
"integrity": "sha512-oSwM7q8PTHQWuZAlp995iPpPJ4Vkl7qT0ZRD+9duL9j2oBy6KcTfyxc8mEuHJYC+z/kbps80aJLkaNzTOrf/kw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": "20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/rimraf/node_modules/lru-cache": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz",
"integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==",
"dev": true,
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/rimraf/node_modules/minimatch": { "node_modules/rimraf/node_modules/minimatch": {
"version": "8.0.4", "version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": "20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/rimraf/node_modules/minipass": { "node_modules/rimraf/node_modules/path-scurry": {
"version": "4.2.8", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"license": "ISC", "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": { "engines": {
"node": ">=8" "node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/ripemd160": { "node_modules/ripemd160": {

38
package.json

@ -1,6 +1,10 @@
{ {
"name": "TimeSafari", "name": "TimeSafari",
"version": "0.3.54-beta", "version": "0.3.54-beta",
"description": "TimeSafari Desktop Application",
"author": {
"name": "TimeSafari Team"
},
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"serve": "vite preview", "serve": "vite preview",
@ -9,7 +13,14 @@
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", "lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js", "prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js",
"test-local": "npx playwright test -c playwright.config-local.ts --trace on", "test-local": "npx playwright test -c playwright.config-local.ts --trace on",
"test-all": "npm run build && npx playwright test -c playwright.config-local.ts --trace on" "test-all": "npm run build && npx playwright test -c playwright.config-local.ts --trace on",
"clean:electron": "rimraf dist-electron dist-electron-build",
"build:electron": "npm run clean:electron && vite build --mode electron && node scripts/build-electron.js",
"build:capacitor": "vite build --mode capacitor",
"build:web": "vite build",
"electron:build:linux": "npm run build:electron && electron-builder --linux",
"electron:build:mac": "npm run build:electron && npx electron-builder --mac",
"electron:build:win": "npm run build:electron && npx electron-builder --win"
}, },
"dependencies": { "dependencies": {
"@capacitor/android": "^6.2.0", "@capacitor/android": "^6.2.0",
@ -104,30 +115,31 @@
"npm-check-updates": "^17.1.13", "npm-check-updates": "^17.1.13",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"rimraf": "^6.0.1",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "~5.2.2", "typescript": "~5.2.2",
"vite": "^5.2.0", "vite": "^5.2.0",
"vite-plugin-pwa": "^0.19.8" "vite-plugin-pwa": "^0.19.8"
}, },
"main": "main.js",
"build": { "build": {
"appId": "com.example.app", "appId": "app.timesafari.app",
"productName": "TimeSafari", "productName": "TimeSafari",
"directories": { "directories": {
"output": "dist-electron-build" "output": "dist-electron-build"
}, },
"files": [ "files": [
"dist-electron/**", {
"src/electron/**" "from": "dist-electron",
"to": ".",
"filter": ["**/*"]
}
], ],
"mac": {
"target": "dmg"
},
"win": {
"target": "nsis"
},
"linux": { "linux": {
"target": "AppImage" "target": ["AppImage"],
"category": "Utility",
"icon": "build/icons"
}, },
"asar": false "asar": true
} }
} }

72
scripts/build-electron.js

@ -0,0 +1,72 @@
const fs = require('fs-extra');
const path = require('path');
async function main() {
try {
console.log('Starting electron build process...');
// Clean directories
const distElectronDir = path.resolve(__dirname, '../dist-electron');
const buildDir = path.resolve(__dirname, '../dist-electron-build');
await fs.emptyDir(distElectronDir);
await fs.emptyDir(buildDir);
console.log('Cleaned directories');
// First build the web app if it doesn't exist
const webDist = path.resolve(__dirname, '../dist');
if (!await fs.pathExists(webDist)) {
console.log('Web dist not found, building web app first...');
throw new Error('Please run \'npm run build\' first to build the web app');
}
// Copy web files to www directory
const wwwDir = path.join(distElectronDir, 'www');
await fs.copy(webDist, wwwDir);
console.log('Copied web files to:', wwwDir);
// Copy and process main.js
const mainJsSrc = path.resolve(__dirname, '../src/electron/main.js');
const mainJsDest = path.join(distElectronDir, 'main.js');
await fs.copy(mainJsSrc, mainJsDest);
console.log('Copied main.js to:', mainJsDest);
// Create the production package.json
const devPackageJson = require('../package.json');
const prodPackageJson = {
name: devPackageJson.name,
version: devPackageJson.version,
description: devPackageJson.description,
author: devPackageJson.author,
main: 'main.js',
private: true
};
await fs.writeJson(
path.join(distElectronDir, 'package.json'),
prodPackageJson,
{ spaces: 2 }
);
// Verify the structure
console.log('\nVerifying build structure:');
const printDir = async (dir, prefix = '') => {
const items = await fs.readdir(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = await fs.stat(fullPath);
console.log(`${prefix}${item}${stat.isDirectory() ? '/' : ''}`);
if (stat.isDirectory()) {
await printDir(fullPath, `${prefix} `);
}
}
};
await printDir(distElectronDir);
console.log('\nBuild completed successfully!');
} catch (error) {
console.error('Build failed:', error);
process.exit(1);
}
}
main().catch(console.error);

69
src/electron/main.js

@ -1,46 +1,44 @@
const { app, BrowserWindow } = require("electron"); const { app, BrowserWindow } = require("electron");
const path = require("path"); const path = require("path");
let mainWindow; function createWindow() {
// Create the browser window.
app.on("ready", () => { const mainWindow = new BrowserWindow({
mainWindow = new BrowserWindow({ width: 1200,
width: 800, height: 800,
height: 600,
webPreferences: { webPreferences: {
//preload: path.join(__dirname, "preload.js"), nodeIntegration: true,
contextIsolation: true, // Security setting contextIsolation: false,
}, },
}); });
const indexPath = path.join( // Log the paths we're trying to load from
__dirname, const appPath = app.getAppPath();
"../../", const indexPath = path.join(appPath, "www", "index.html");
"dist-electron", console.log("App path:", appPath);
"index.html", console.log("Trying to load from:", indexPath);
);
console.log("Loading Vue app from:", indexPath);
mainWindow.webContents.openDevTools();
mainWindow.webContents.on(
"did-fail-load",
(event, errorCode, errorDescription, validatedURL) => {
console.error(
"Failed to load:",
validatedURL,
"Error:",
errorDescription,
);
},
);
mainWindow.webContents.on("console-message", (event, level, message) => {
console.log(`[Renderer] ${message}`);
});
// Load the index.html file
mainWindow.loadFile(indexPath).catch((err) => { mainWindow.loadFile(indexPath).catch((err) => {
console.error("Failed to load index.html:", err); console.error("Failed to load index.html:", err);
// Try to list directory contents to debug
const fs = require("fs");
console.log("Directory contents:", fs.readdirSync(appPath));
});
// Open the DevTools in development
if (process.env.NODE_ENV === "development") {
mainWindow.webContents.openDevTools();
}
}
app.whenReady().then(() => {
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
}); });
}); });
@ -49,3 +47,8 @@ app.on("window-all-closed", () => {
app.quit(); app.quit();
} }
}); });
// Handle any errors
process.on("uncaughtException", (error) => {
console.error("Uncaught Exception:", error);
});

16
vite.config.mjs

@ -3,6 +3,7 @@ import { VitePWA } from "vite-plugin-pwa";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import dotenv from "dotenv"; import dotenv from "dotenv";
import { loadAppConfig } from "./vite.config.utils"; import { loadAppConfig } from "./vite.config.utils";
import path from "path";
// Load environment variables from .env file // Load environment variables from .env file
dotenv.config(); dotenv.config();
@ -27,7 +28,20 @@ export default defineConfig(({ mode }) => {
port: process.env.VITE_PORT || 8080, port: process.env.VITE_PORT || 8080,
}, },
build: { build: {
outDir, // Dynamically set output directory outDir,
rollupOptions: {
...(isElectron && {
input: {
main: path.resolve(__dirname, 'src/electron/main.js'),
index: path.resolve(__dirname, 'index.html')
},
output: {
dir: outDir,
format: 'cjs',
entryFileNames: '[name].js'
}
})
}
}, },
plugins: [ plugins: [
vue(), vue(),

Loading…
Cancel
Save