Browse Source
- Enhance electron build configuration with proper asset handling - Add comprehensive logging and error tracking - Implement CSP headers for security - Fix module exports for logger compatibility - Update TypeScript and Vite configs for better build support - Improve development workflow with better dev tools integrationpull/133/head
7 changed files with 506 additions and 90 deletions
@ -1,98 +1,243 @@ |
|||
const fs = require('fs'); |
|||
const path = require('path'); |
|||
const fs = require('fs-extra'); |
|||
|
|||
async function main() { |
|||
try { |
|||
console.log('Starting electron build process...'); |
|||
|
|||
// Create dist directory if it doesn't exist
|
|||
const distElectronDir = path.resolve(__dirname, '../dist-electron'); |
|||
await fs.ensureDir(distElectronDir); |
|||
|
|||
// Copy web files
|
|||
const wwwDir = path.join(distElectronDir, 'www'); |
|||
await fs.ensureDir(wwwDir); |
|||
await fs.copy('dist', wwwDir); |
|||
const webDistPath = path.join(__dirname, '..', 'dist'); |
|||
const electronDistPath = path.join(__dirname, '..', 'dist-electron'); |
|||
const wwwPath = path.join(electronDistPath, 'www'); |
|||
|
|||
// Create www directory if it doesn't exist
|
|||
if (!fs.existsSync(wwwPath)) { |
|||
fs.mkdirSync(wwwPath, { recursive: true }); |
|||
} |
|||
|
|||
// Copy and fix index.html
|
|||
const indexPath = path.join(wwwDir, 'index.html'); |
|||
let indexContent = await fs.readFile(indexPath, 'utf8'); |
|||
// Copy web files to www directory
|
|||
fs.cpSync(webDistPath, wwwPath, { recursive: true }); |
|||
|
|||
// More comprehensive path fixing
|
|||
// Fix asset paths in index.html
|
|||
const indexPath = path.join(wwwPath, 'index.html'); |
|||
let indexContent = fs.readFileSync(indexPath, 'utf8'); |
|||
|
|||
// Fix asset paths
|
|||
indexContent = indexContent |
|||
// Fix absolute paths to be relative
|
|||
.replace(/src="\//g, 'src="\./') |
|||
.replace(/href="\//g, 'href="\./') |
|||
// Fix modulepreload paths
|
|||
.replace(/<link [^>]*rel="modulepreload"[^>]*href="\/assets\//g, '<link rel="modulepreload" as="script" crossorigin="" href="./assets/') |
|||
.replace(/<link [^>]*rel="modulepreload"[^>]*href="\.\/assets\//g, '<link rel="modulepreload" as="script" crossorigin="" href="./assets/') |
|||
// Fix stylesheet paths
|
|||
.replace(/<link [^>]*rel="stylesheet"[^>]*href="\/assets\//g, '<link rel="stylesheet" crossorigin="" href="./assets/') |
|||
.replace(/<link [^>]*rel="stylesheet"[^>]*href="\.\/assets\//g, '<link rel="stylesheet" crossorigin="" href="./assets/') |
|||
// Fix script paths
|
|||
.replace(/src="\/assets\//g, 'src="./assets/') |
|||
.replace(/src="\.\/assets\//g, 'src="./assets/') |
|||
// Fix any remaining asset paths
|
|||
.replace(/(['"]\/?)(assets\/)/g, '"./assets/'); |
|||
|
|||
// Debug output
|
|||
console.log('After path fixing, checking for remaining /assets/ paths:', indexContent.includes('/assets/')); |
|||
console.log('Sample of fixed content:', indexContent.slice(0, 500)); |
|||
.replace(/\/assets\//g, './assets/') |
|||
.replace(/href="\//g, 'href="./') |
|||
.replace(/src="\//g, 'src="./'); |
|||
|
|||
await fs.writeFile(indexPath, indexContent); |
|||
fs.writeFileSync(indexPath, indexContent); |
|||
|
|||
console.log('Copied and fixed web files in:', wwwDir); |
|||
// Check for remaining /assets/ paths
|
|||
console.log('After path fixing, checking for remaining /assets/ paths:', indexContent.includes('/assets/')); |
|||
console.log('Sample of fixed content:', indexContent.substring(0, 500)); |
|||
|
|||
console.log('Copied and fixed web files in:', wwwPath); |
|||
|
|||
// Copy main process files
|
|||
console.log('Copying main process files...'); |
|||
const mainProcessFiles = [ |
|||
['src/electron/main.js', 'main.js'], |
|||
['src/electron/preload.js', 'preload.js'] |
|||
]; |
|||
|
|||
for (const [src, dest] of mainProcessFiles) { |
|||
const destPath = path.join(distElectronDir, dest); |
|||
console.log(`Copying ${src} to ${destPath}`); |
|||
await fs.copy(src, destPath); |
|||
} |
|||
|
|||
// Create package.json for production
|
|||
const devPackageJson = require('../package.json'); |
|||
const prodPackageJson = { |
|||
name: devPackageJson.name, |
|||
version: devPackageJson.version, |
|||
description: devPackageJson.description, |
|||
author: devPackageJson.author, |
|||
main: 'main.js', |
|||
private: true, |
|||
// Create the main process file with inlined logger
|
|||
const mainContent = `const { app, BrowserWindow } = require("electron");
|
|||
const path = require("path"); |
|||
const fs = require("fs"); |
|||
|
|||
// Inline logger implementation
|
|||
const logger = { |
|||
log: (...args) => console.log(...args), |
|||
error: (...args) => console.error(...args), |
|||
info: (...args) => console.info(...args), |
|||
warn: (...args) => console.warn(...args), |
|||
debug: (...args) => console.debug(...args), |
|||
}; |
|||
|
|||
await fs.writeJson( |
|||
path.join(distElectronDir, 'package.json'), |
|||
prodPackageJson, |
|||
{ spaces: 2 } |
|||
// Check if running in dev mode
|
|||
const isDev = process.argv.includes("--inspect"); |
|||
|
|||
function createWindow() { |
|||
// Add before createWindow function
|
|||
const preloadPath = path.join(__dirname, "preload.js"); |
|||
logger.log("Checking preload path:", preloadPath); |
|||
logger.log("Preload exists:", fs.existsSync(preloadPath)); |
|||
|
|||
// Create the browser window.
|
|||
const mainWindow = new BrowserWindow({ |
|||
width: 1200, |
|||
height: 800, |
|||
webPreferences: { |
|||
nodeIntegration: false, |
|||
contextIsolation: true, |
|||
webSecurity: true, |
|||
allowRunningInsecureContent: false, |
|||
preload: path.join(__dirname, "preload.js"), |
|||
}, |
|||
}); |
|||
|
|||
// Always open DevTools for now
|
|||
mainWindow.webContents.openDevTools(); |
|||
|
|||
// Intercept requests to fix asset paths
|
|||
mainWindow.webContents.session.webRequest.onBeforeRequest( |
|||
{ |
|||
urls: [ |
|||
"file://*/*/assets/*", |
|||
"file://*/assets/*", |
|||
"file:///assets/*", // Catch absolute paths
|
|||
"<all_urls>", // Catch all URLs as a fallback
|
|||
], |
|||
}, |
|||
(details, callback) => { |
|||
let url = details.url; |
|||
|
|||
// Handle paths that don't start with file://
|
|||
if (!url.startsWith("file://") && url.includes("/assets/")) { |
|||
url = \`file://\${path.join(__dirname, "www", url)}\`;
|
|||
} |
|||
|
|||
// Handle absolute paths starting with /assets/
|
|||
if (url.includes("/assets/") && !url.includes("/www/assets/")) { |
|||
const baseDir = url.includes("dist-electron") |
|||
? url.substring( |
|||
0, |
|||
url.indexOf("/dist-electron") + "/dist-electron".length, |
|||
) |
|||
: \`file://\${__dirname}\`;
|
|||
const assetPath = url.split("/assets/")[1]; |
|||
const newUrl = \`\${baseDir}/www/assets/\${assetPath}\`;
|
|||
callback({ redirectURL: newUrl }); |
|||
return; |
|||
} |
|||
|
|||
callback({}); // No redirect for other URLs
|
|||
}, |
|||
); |
|||
|
|||
// Verify the build
|
|||
console.log('\nVerifying build structure:'); |
|||
const files = await fs.readdir(distElectronDir); |
|||
console.log('Files in dist-electron:', files); |
|||
if (isDev) { |
|||
// Debug info
|
|||
logger.log("Debug Info:"); |
|||
logger.log("Running in dev mode:", isDev); |
|||
logger.log("App is packaged:", app.isPackaged); |
|||
logger.log("Process resource path:", process.resourcesPath); |
|||
logger.log("App path:", app.getAppPath()); |
|||
logger.log("__dirname:", __dirname); |
|||
logger.log("process.cwd():", process.cwd()); |
|||
} |
|||
|
|||
const indexPath = path.join(__dirname, "www", "index.html"); |
|||
|
|||
if (!files.includes('main.js')) { |
|||
throw new Error('main.js not found in build directory'); |
|||
if (isDev) { |
|||
logger.log("Loading index from:", indexPath); |
|||
logger.log("www path:", path.join(__dirname, "www")); |
|||
logger.log("www assets path:", path.join(__dirname, "www", "assets")); |
|||
} |
|||
if (!files.includes('preload.js')) { |
|||
throw new Error('preload.js not found in build directory'); |
|||
|
|||
if (!fs.existsSync(indexPath)) { |
|||
logger.error(\`Index file not found at: \${indexPath}\`);
|
|||
throw new Error("Index file not found"); |
|||
} |
|||
if (!files.includes('package.json')) { |
|||
throw new Error('package.json not found in build directory'); |
|||
|
|||
// Add CSP headers to allow API connections, Google Fonts, and zxing-wasm
|
|||
mainWindow.webContents.session.webRequest.onHeadersReceived( |
|||
(details, callback) => { |
|||
callback({ |
|||
responseHeaders: { |
|||
...details.responseHeaders, |
|||
"Content-Security-Policy": [ |
|||
"default-src 'self';" + |
|||
"connect-src 'self' https://api.endorser.ch https://*.timesafari.app https://*.jsdelivr.net;" + |
|||
"img-src 'self' data: https: blob:;" + |
|||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.jsdelivr.net;" + |
|||
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;" + |
|||
"font-src 'self' data: https://fonts.gstatic.com;" + |
|||
"style-src-elem 'self' 'unsafe-inline' https://fonts.googleapis.com;" + |
|||
"worker-src 'self' blob:;", |
|||
], |
|||
}, |
|||
}); |
|||
}, |
|||
); |
|||
|
|||
// Load the index.html
|
|||
mainWindow |
|||
.loadFile(indexPath) |
|||
.then(() => { |
|||
logger.log("Successfully loaded index.html"); |
|||
if (isDev) { |
|||
mainWindow.webContents.openDevTools(); |
|||
logger.log("DevTools opened - running in dev mode"); |
|||
} |
|||
}) |
|||
.catch((err) => { |
|||
logger.error("Failed to load index.html:", err); |
|||
logger.error("Attempted path:", indexPath); |
|||
}); |
|||
|
|||
// Listen for console messages from the renderer
|
|||
mainWindow.webContents.on("console-message", (_event, _level, message) => { |
|||
logger.log("Renderer Console:", message); |
|||
}); |
|||
|
|||
// Add right after creating the BrowserWindow
|
|||
mainWindow.webContents.on( |
|||
"did-fail-load", |
|||
(_event, errorCode, errorDescription) => { |
|||
logger.error("Page failed to load:", errorCode, errorDescription); |
|||
}, |
|||
); |
|||
|
|||
console.log('Build completed successfully!'); |
|||
} catch (error) { |
|||
console.error('Build failed:', error); |
|||
process.exit(1); |
|||
mainWindow.webContents.on("preload-error", (_event, preloadPath, error) => { |
|||
logger.error("Preload script error:", preloadPath, error); |
|||
}); |
|||
|
|||
mainWindow.webContents.on( |
|||
"console-message", |
|||
(_event, _level, message, line, sourceId) => { |
|||
logger.log("Renderer Console:", line, sourceId, message); |
|||
}, |
|||
); |
|||
|
|||
// Enable remote debugging when in dev mode
|
|||
if (isDev) { |
|||
mainWindow.webContents.openDevTools(); |
|||
} |
|||
} |
|||
|
|||
// Handle app ready
|
|||
app.whenReady().then(createWindow); |
|||
|
|||
// Handle all windows closed
|
|||
app.on("window-all-closed", () => { |
|||
if (process.platform !== "darwin") { |
|||
app.quit(); |
|||
} |
|||
}); |
|||
|
|||
app.on("activate", () => { |
|||
if (BrowserWindow.getAllWindows().length === 0) { |
|||
createWindow(); |
|||
} |
|||
}); |
|||
|
|||
// Handle any errors
|
|||
process.on("uncaughtException", (error) => { |
|||
logger.error("Uncaught Exception:", error); |
|||
}); |
|||
`;
|
|||
|
|||
// Write the main process file
|
|||
const mainDest = path.join(electronDistPath, 'main.js'); |
|||
fs.writeFileSync(mainDest, mainContent); |
|||
|
|||
// Copy preload script if it exists
|
|||
const preloadSrc = path.join(__dirname, '..', 'src', 'electron', 'preload.js'); |
|||
const preloadDest = path.join(electronDistPath, 'preload.js'); |
|||
if (fs.existsSync(preloadSrc)) { |
|||
console.log(`Copying ${preloadSrc} to ${preloadDest}`); |
|||
fs.copyFileSync(preloadSrc, preloadDest); |
|||
} |
|||
|
|||
// Verify build structure
|
|||
console.log('\nVerifying build structure:'); |
|||
console.log('Files in dist-electron:', fs.readdirSync(electronDistPath)); |
|||
|
|||
main(); |
|||
console.log('Build completed successfully!'); |
@ -0,0 +1,183 @@ |
|||
import { app, BrowserWindow } from "electron"; |
|||
import path from "path"; |
|||
import fs from "fs"; |
|||
|
|||
// Simple logger implementation
|
|||
const logger = { |
|||
log: (...args: unknown[]) => console.log(...args), |
|||
error: (...args: unknown[]) => console.error(...args), |
|||
info: (...args: unknown[]) => console.info(...args), |
|||
warn: (...args: unknown[]) => console.warn(...args), |
|||
debug: (...args: unknown[]) => console.debug(...args), |
|||
}; |
|||
|
|||
// Check if running in dev mode
|
|||
const isDev = process.argv.includes("--inspect"); |
|||
|
|||
function createWindow(): void { |
|||
// Add before createWindow function
|
|||
const preloadPath = path.join(__dirname, "preload.js"); |
|||
logger.log("Checking preload path:", preloadPath); |
|||
logger.log("Preload exists:", fs.existsSync(preloadPath)); |
|||
|
|||
// Create the browser window.
|
|||
const mainWindow = new BrowserWindow({ |
|||
width: 1200, |
|||
height: 800, |
|||
webPreferences: { |
|||
nodeIntegration: false, |
|||
contextIsolation: true, |
|||
webSecurity: true, |
|||
allowRunningInsecureContent: false, |
|||
preload: path.join(__dirname, "preload.js"), |
|||
}, |
|||
}); |
|||
|
|||
// Always open DevTools for now
|
|||
mainWindow.webContents.openDevTools(); |
|||
|
|||
// Intercept requests to fix asset paths
|
|||
mainWindow.webContents.session.webRequest.onBeforeRequest( |
|||
{ |
|||
urls: [ |
|||
"file://*/*/assets/*", |
|||
"file://*/assets/*", |
|||
"file:///assets/*", // Catch absolute paths
|
|||
"<all_urls>", // Catch all URLs as a fallback
|
|||
], |
|||
}, |
|||
(details, callback) => { |
|||
let url = details.url; |
|||
|
|||
// Handle paths that don't start with file://
|
|||
if (!url.startsWith("file://") && url.includes("/assets/")) { |
|||
url = `file://${path.join(__dirname, "www", url)}`; |
|||
} |
|||
|
|||
// Handle absolute paths starting with /assets/
|
|||
if (url.includes("/assets/") && !url.includes("/www/assets/")) { |
|||
const baseDir = url.includes("dist-electron") |
|||
? url.substring( |
|||
0, |
|||
url.indexOf("/dist-electron") + "/dist-electron".length, |
|||
) |
|||
: `file://${__dirname}`; |
|||
const assetPath = url.split("/assets/")[1]; |
|||
const newUrl = `${baseDir}/www/assets/${assetPath}`; |
|||
callback({ redirectURL: newUrl }); |
|||
return; |
|||
} |
|||
|
|||
callback({}); // No redirect for other URLs
|
|||
}, |
|||
); |
|||
|
|||
if (isDev) { |
|||
// Debug info
|
|||
logger.log("Debug Info:"); |
|||
logger.log("Running in dev mode:", isDev); |
|||
logger.log("App is packaged:", app.isPackaged); |
|||
logger.log("Process resource path:", process.resourcesPath); |
|||
logger.log("App path:", app.getAppPath()); |
|||
logger.log("__dirname:", __dirname); |
|||
logger.log("process.cwd():", process.cwd()); |
|||
} |
|||
|
|||
const indexPath = path.join(__dirname, "www", "index.html"); |
|||
|
|||
if (isDev) { |
|||
logger.log("Loading index from:", indexPath); |
|||
logger.log("www path:", path.join(__dirname, "www")); |
|||
logger.log("www assets path:", path.join(__dirname, "www", "assets")); |
|||
} |
|||
|
|||
if (!fs.existsSync(indexPath)) { |
|||
logger.error(`Index file not found at: ${indexPath}`); |
|||
throw new Error("Index file not found"); |
|||
} |
|||
|
|||
// Add CSP headers to allow API connections
|
|||
mainWindow.webContents.session.webRequest.onHeadersReceived( |
|||
(details, callback) => { |
|||
callback({ |
|||
responseHeaders: { |
|||
...details.responseHeaders, |
|||
"Content-Security-Policy": [ |
|||
"default-src 'self';" + |
|||
"connect-src 'self' https://api.endorser.ch https://*.timesafari.app;" + |
|||
"img-src 'self' data: https: blob:;" + |
|||
"script-src 'self' 'unsafe-inline' 'unsafe-eval';" + |
|||
"style-src 'self' 'unsafe-inline';" + |
|||
"font-src 'self' data:;", |
|||
], |
|||
}, |
|||
}); |
|||
}, |
|||
); |
|||
|
|||
// Load the index.html
|
|||
mainWindow |
|||
.loadFile(indexPath) |
|||
.then(() => { |
|||
logger.log("Successfully loaded index.html"); |
|||
if (isDev) { |
|||
mainWindow.webContents.openDevTools(); |
|||
logger.log("DevTools opened - running in dev mode"); |
|||
} |
|||
}) |
|||
.catch((err) => { |
|||
logger.error("Failed to load index.html:", err); |
|||
logger.error("Attempted path:", indexPath); |
|||
}); |
|||
|
|||
// Listen for console messages from the renderer
|
|||
mainWindow.webContents.on("console-message", (_event, _level, message) => { |
|||
logger.log("Renderer Console:", message); |
|||
}); |
|||
|
|||
// Add right after creating the BrowserWindow
|
|||
mainWindow.webContents.on( |
|||
"did-fail-load", |
|||
(_event, errorCode, errorDescription) => { |
|||
logger.error("Page failed to load:", errorCode, errorDescription); |
|||
}, |
|||
); |
|||
|
|||
mainWindow.webContents.on("preload-error", (_event, preloadPath, error) => { |
|||
logger.error("Preload script error:", preloadPath, error); |
|||
}); |
|||
|
|||
mainWindow.webContents.on( |
|||
"console-message", |
|||
(_event, _level, message, line, sourceId) => { |
|||
logger.log("Renderer Console:", line, sourceId, message); |
|||
}, |
|||
); |
|||
|
|||
// Enable remote debugging when in dev mode
|
|||
if (isDev) { |
|||
mainWindow.webContents.openDevTools(); |
|||
} |
|||
} |
|||
|
|||
// Handle app ready
|
|||
app.whenReady().then(createWindow); |
|||
|
|||
// Handle all windows closed
|
|||
app.on("window-all-closed", () => { |
|||
if (process.platform !== "darwin") { |
|||
app.quit(); |
|||
} |
|||
}); |
|||
|
|||
app.on("activate", () => { |
|||
if (BrowserWindow.getAllWindows().length === 0) { |
|||
createWindow(); |
|||
} |
|||
}); |
|||
|
|||
// Handle any errors
|
|||
process.on("uncaughtException", (error) => { |
|||
logger.error("Uncaught Exception:", error); |
|||
}); |
|||
|
@ -0,0 +1,26 @@ |
|||
{ |
|||
"extends": "./tsconfig.json", |
|||
"compilerOptions": { |
|||
"module": "ESNext", |
|||
"moduleResolution": "bundler", |
|||
"target": "ES2020", |
|||
"outDir": "dist-electron", |
|||
"rootDir": "src", |
|||
"sourceMap": true, |
|||
"esModuleInterop": true, |
|||
"allowJs": true, |
|||
"resolveJsonModule": true, |
|||
"isolatedModules": true, |
|||
"noEmit": true, |
|||
"allowImportingTsExtensions": true, |
|||
"types": ["vite/client"], |
|||
"paths": { |
|||
"@/*": ["./src/*"] |
|||
} |
|||
}, |
|||
"include": [ |
|||
"src/electron/**/*.ts", |
|||
"src/utils/**/*.ts", |
|||
"src/constants/**/*.ts" |
|||
] |
|||
} |
Loading…
Reference in new issue