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 path = require('path'); |
||||
const fs = require('fs-extra'); |
|
||||
|
|
||||
async function main() { |
|
||||
try { |
|
||||
console.log('Starting electron build process...'); |
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
|
// Copy web files
|
||||
const wwwDir = path.join(distElectronDir, 'www'); |
const webDistPath = path.join(__dirname, '..', 'dist'); |
||||
await fs.ensureDir(wwwDir); |
const electronDistPath = path.join(__dirname, '..', 'dist-electron'); |
||||
await fs.copy('dist', wwwDir); |
const wwwPath = path.join(electronDistPath, 'www'); |
||||
|
|
||||
|
// Create www directory if it doesn't exist
|
||||
|
if (!fs.existsSync(wwwPath)) { |
||||
|
fs.mkdirSync(wwwPath, { recursive: true }); |
||||
|
} |
||||
|
|
||||
|
// Copy web files to www directory
|
||||
|
fs.cpSync(webDistPath, wwwPath, { recursive: true }); |
||||
|
|
||||
// Copy and fix index.html
|
// Fix asset paths in index.html
|
||||
const indexPath = path.join(wwwDir, 'index.html'); |
const indexPath = path.join(wwwPath, 'index.html'); |
||||
let indexContent = await fs.readFile(indexPath, 'utf8'); |
let indexContent = fs.readFileSync(indexPath, 'utf8'); |
||||
|
|
||||
// More comprehensive path fixing
|
// Fix asset paths
|
||||
indexContent = indexContent |
indexContent = indexContent |
||||
// Fix absolute paths to be relative
|
.replace(/\/assets\//g, './assets/') |
||||
.replace(/src="\//g, 'src="\./') |
.replace(/href="\//g, 'href="./') |
||||
.replace(/href="\//g, 'href="\./') |
.replace(/src="\//g, 'src="./'); |
||||
// Fix modulepreload paths
|
|
||||
.replace(/<link [^>]*rel="modulepreload"[^>]*href="\/assets\//g, '<link rel="modulepreload" as="script" crossorigin="" href="./assets/') |
fs.writeFileSync(indexPath, indexContent); |
||||
.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)); |
|
||||
|
|
||||
await fs.writeFile(indexPath, indexContent); |
// 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:', wwwDir); |
console.log('Copied and fixed web files in:', wwwPath); |
||||
|
|
||||
// Copy main process files
|
// Copy main process files
|
||||
console.log('Copying main process files...'); |
console.log('Copying main process files...'); |
||||
const mainProcessFiles = [ |
|
||||
['src/electron/main.js', 'main.js'], |
// Create the main process file with inlined logger
|
||||
['src/electron/preload.js', 'preload.js'] |
const mainContent = `const { app, BrowserWindow } = require("electron");
|
||||
]; |
const path = require("path"); |
||||
|
const fs = require("fs"); |
||||
for (const [src, dest] of mainProcessFiles) { |
|
||||
const destPath = path.join(distElectronDir, dest); |
// Inline logger implementation
|
||||
console.log(`Copying ${src} to ${destPath}`); |
const logger = { |
||||
await fs.copy(src, destPath); |
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), |
||||
|
}; |
||||
|
|
||||
|
// 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; |
||||
} |
} |
||||
|
|
||||
// Create package.json for production
|
callback({}); // No redirect for other URLs
|
||||
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 build
|
if (isDev) { |
||||
console.log('\nVerifying build structure:'); |
// Debug info
|
||||
const files = await fs.readdir(distElectronDir); |
logger.log("Debug Info:"); |
||||
console.log('Files in dist-electron:', files); |
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()); |
||||
|
} |
||||
|
|
||||
if (!files.includes('main.js')) { |
const indexPath = path.join(__dirname, "www", "index.html"); |
||||
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); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
mainWindow.webContents.on("preload-error", (_event, preloadPath, error) => { |
||||
|
logger.error("Preload script error:", preloadPath, error); |
||||
|
}); |
||||
|
|
||||
console.log('Build completed successfully!'); |
mainWindow.webContents.on( |
||||
} catch (error) { |
"console-message", |
||||
console.error('Build failed:', error); |
(_event, _level, message, line, sourceId) => { |
||||
process.exit(1); |
logger.log("Renderer Console:", line, sourceId, message); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// Enable remote debugging when in dev mode
|
||||
|
if (isDev) { |
||||
|
mainWindow.webContents.openDevTools(); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
main(); |
// 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)); |
||||
|
|
||||
|
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