mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
278 lines
7.9 KiB
TypeScript
278 lines
7.9 KiB
TypeScript
/// <reference types="vite/client" />
|
|
import { createRequestHandler } from '@remix-run/node';
|
|
import electron, { app, BrowserWindow, ipcMain, protocol, session } from 'electron';
|
|
import log from 'electron-log';
|
|
import path from 'node:path';
|
|
import * as pkg from '../../package.json';
|
|
import dotenv from 'dotenv';
|
|
import { setupAutoUpdater } from './utils/auto-update';
|
|
import { isDev, DEFAULT_PORT } from './utils/constants';
|
|
import { initViteServer, viteServer } from './utils/vite-server';
|
|
import { setupMenu } from './ui/menu';
|
|
import { createWindow } from './ui/window';
|
|
import { initCookies, storeCookies } from './utils/cookie';
|
|
import { loadServerBuild, serveAsset } from './utils/serve';
|
|
import { reloadOnChange } from './utils/reload';
|
|
|
|
Object.assign(console, log.functions);
|
|
|
|
// Load environment variables from .env file
|
|
let envPath = '';
|
|
|
|
try {
|
|
// In development mode
|
|
|
|
/* eslint-disable-next-line */
|
|
if (isDev) {
|
|
envPath = path.join(app.getAppPath(), '.env');
|
|
|
|
const result = dotenv.config({ path: envPath });
|
|
|
|
if (result.error) {
|
|
console.log('Error loading .env file in development:', result.error.message);
|
|
} else {
|
|
console.log('Loaded environment variables from:', envPath);
|
|
}
|
|
} else {
|
|
/*
|
|
* In production/packaged mode, environment variables should be baked into the build
|
|
* or handled differently as .env files aren't typically accessible in packaged apps
|
|
*/
|
|
console.log('Running in production mode, using bundled environment variables');
|
|
|
|
/*
|
|
* For critical environment variables that must be available in production,
|
|
* you can set them directly here or read from a config file included in the package
|
|
*/
|
|
// process.env.CRITICAL_VARIABLE = 'some-value';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error setting up environment variables:', error);
|
|
}
|
|
|
|
// Safely access import.meta.env if available
|
|
console.debug('main: import.meta.env:', typeof import.meta !== 'undefined' ? import.meta.env : 'Not available');
|
|
console.log('main: isDev:', isDev);
|
|
console.log('NODE_ENV:', global.process.env.NODE_ENV);
|
|
console.log('isPackaged:', app.isPackaged);
|
|
|
|
// Log unhandled errors
|
|
process.on('uncaughtException', async (error) => {
|
|
console.log('Uncaught Exception:', error);
|
|
});
|
|
|
|
process.on('unhandledRejection', async (error) => {
|
|
console.log('Unhandled Rejection:', error);
|
|
});
|
|
|
|
(() => {
|
|
/* eslint-disable-next-line */
|
|
const root = global.process.env.APP_PATH_ROOT ??
|
|
(typeof import.meta !== 'undefined' ? import.meta.env.VITE_APP_PATH_ROOT : undefined);
|
|
|
|
if (root === undefined) {
|
|
console.log('no given APP_PATH_ROOT or VITE_APP_PATH_ROOT. default path is used.');
|
|
return;
|
|
}
|
|
|
|
if (!path.isAbsolute(root)) {
|
|
console.log('APP_PATH_ROOT must be absolute path.');
|
|
global.process.exit(1);
|
|
}
|
|
|
|
console.log(`APP_PATH_ROOT: ${root}`);
|
|
|
|
const subdirName = pkg.name;
|
|
|
|
for (const [key, val] of [
|
|
['appData', ''],
|
|
['userData', subdirName],
|
|
['sessionData', subdirName],
|
|
] as const) {
|
|
app.setPath(key, path.join(root, val));
|
|
}
|
|
|
|
app.setAppLogsPath(path.join(root, subdirName, 'Logs'));
|
|
})();
|
|
|
|
console.log('appPath:', app.getAppPath());
|
|
|
|
const keys: Parameters<typeof app.getPath>[number][] = ['home', 'appData', 'userData', 'sessionData', 'logs', 'temp'];
|
|
keys.forEach((key) => console.log(`${key}:`, app.getPath(key)));
|
|
console.log('start whenReady');
|
|
|
|
declare global {
|
|
// eslint-disable-next-line no-var, @typescript-eslint/naming-convention
|
|
var __electron__: typeof electron;
|
|
}
|
|
|
|
(async () => {
|
|
await app.whenReady();
|
|
console.log('App is ready');
|
|
|
|
// Load any existing cookies from ElectronStore, set as cookie
|
|
await initCookies();
|
|
|
|
const serverBuild = await loadServerBuild();
|
|
|
|
protocol.handle('http', async (req) => {
|
|
console.log('Handling request for:', req.url);
|
|
|
|
if (isDev) {
|
|
console.log('Dev mode: forwarding to vite server');
|
|
|
|
try {
|
|
const response = await fetch(req);
|
|
return response;
|
|
} catch (err) {
|
|
console.error('Error forwarding to vite server:', err);
|
|
return new Response(
|
|
`Error forwarding request to vite server: ${err instanceof Error ? err.message : String(err)}`,
|
|
{
|
|
status: 500,
|
|
headers: { 'content-type': 'text/plain' },
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
req.headers.append('Referer', req.referrer);
|
|
|
|
try {
|
|
const url = new URL(req.url);
|
|
|
|
// Forward requests to specific local server ports
|
|
if (url.port !== `${DEFAULT_PORT}`) {
|
|
console.log('Forwarding request to local server:', req.url);
|
|
return await fetch(req);
|
|
}
|
|
|
|
// Always try to serve asset first
|
|
const assetPath = path.join(app.getAppPath(), 'build', 'client');
|
|
const res = await serveAsset(req, assetPath);
|
|
|
|
if (res) {
|
|
console.log('Served asset:', req.url);
|
|
return res;
|
|
}
|
|
|
|
// Forward all cookies to remix server
|
|
const cookies = await session.defaultSession.cookies.get({});
|
|
|
|
if (cookies.length > 0) {
|
|
req.headers.set('Cookie', cookies.map((c) => `${c.name}=${c.value}`).join('; '));
|
|
|
|
// Store all cookies
|
|
await storeCookies(cookies);
|
|
}
|
|
|
|
// Create request handler with the server build
|
|
const handler = createRequestHandler(serverBuild, 'production');
|
|
console.log('Handling request with server build:', req.url);
|
|
|
|
const result = await handler(req, {
|
|
/*
|
|
* Remix app access cloudflare.env
|
|
* Need to pass an empty object to prevent undefined
|
|
*/
|
|
// @ts-ignore:next-line
|
|
cloudflare: {},
|
|
});
|
|
|
|
return result;
|
|
} catch (err) {
|
|
console.log('Error handling request:', {
|
|
url: req.url,
|
|
error:
|
|
err instanceof Error
|
|
? {
|
|
message: err.message,
|
|
stack: err.stack,
|
|
cause: err.cause,
|
|
}
|
|
: err,
|
|
});
|
|
|
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
|
|
return new Response(`Error handling request to ${req.url}: ${error.stack ?? error.message}`, {
|
|
status: 500,
|
|
headers: { 'content-type': 'text/plain' },
|
|
});
|
|
}
|
|
});
|
|
|
|
const rendererURL = await (isDev
|
|
? (async () => {
|
|
await initViteServer();
|
|
|
|
if (!viteServer) {
|
|
throw new Error('Vite server is not initialized');
|
|
}
|
|
|
|
const listen = await viteServer.listen();
|
|
global.__electron__ = electron;
|
|
viteServer.printUrls();
|
|
|
|
return `http://localhost:${listen.config.server.port}`;
|
|
})()
|
|
: `http://localhost:${DEFAULT_PORT}`);
|
|
|
|
console.log('Using renderer URL:', rendererURL);
|
|
|
|
const win = await createWindow(rendererURL);
|
|
|
|
app.on('activate', async () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
await createWindow(rendererURL);
|
|
}
|
|
});
|
|
|
|
console.log('end whenReady');
|
|
|
|
return win;
|
|
})()
|
|
.then((win) => {
|
|
// IPC samples : send and recieve.
|
|
let count = 0;
|
|
setInterval(() => win.webContents.send('ping', `hello from main! ${count++}`), 60 * 1000);
|
|
ipcMain.handle('ipcTest', (event, ...args) => console.log('ipc: renderer -> main', { event, ...args }));
|
|
|
|
return win;
|
|
})
|
|
.then((win) => {
|
|
/* eslint-disable-next-line */
|
|
// Add navigationHistory to Electron's WebContents prototype if it doesn't exist
|
|
const webContentsProto = Object.getPrototypeOf(win.webContents);
|
|
|
|
if (!('navigationHistory' in webContentsProto)) {
|
|
Object.defineProperty(webContentsProto, 'navigationHistory', {
|
|
get() {
|
|
return {
|
|
goBack: () => {
|
|
if (this.canGoBack()) {
|
|
this.goBack();
|
|
}
|
|
},
|
|
goForward: () => {
|
|
if (this.canGoForward()) {
|
|
this.goForward();
|
|
}
|
|
},
|
|
};
|
|
},
|
|
});
|
|
}
|
|
|
|
return setupMenu(win);
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform !== 'darwin') {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
reloadOnChange();
|
|
setupAutoUpdater();
|