fix(standalone): fix standalone mode to run without Electron
Three issues prevented standalone (non-Electron) mode from working: 1. sentry.ts used a top-level `import * from '@sentry/electron/main'` which crashes in plain Node.js. Changed to a try/catch require() so the module is safe to import in both environments. 2. vite.standalone.config.ts resolved all paths relative to __dirname (docker/) but is invoked from the repo root. Fixed to resolve relative to the repo root via a ROOT constant. 3. The electron stub was missing `safeStorage` and `screen` exports that newer code imports. Added them, and externalized agent-teams-controller (plain CJS with relative requires that break when bundled by Vite).
This commit is contained in:
parent
d6ee7bc320
commit
8e84961d4a
2 changed files with 46 additions and 20 deletions
|
|
@ -10,6 +10,11 @@ import { defineConfig } from 'vite'
|
||||||
|
|
||||||
import type { Plugin } from 'vite'
|
import type { Plugin } from 'vite'
|
||||||
|
|
||||||
|
// This config lives in docker/ but is invoked from the repo root via
|
||||||
|
// `vite build --config docker/vite.standalone.config.ts`, so __dirname
|
||||||
|
// is docker/. All paths must resolve relative to the repo root.
|
||||||
|
const ROOT = resolve(__dirname, '..')
|
||||||
|
|
||||||
// Node.js built-in modules that should be externalized
|
// Node.js built-in modules that should be externalized
|
||||||
const nodeBuiltins = new Set([
|
const nodeBuiltins = new Set([
|
||||||
'fs', 'path', 'os', 'events', 'stream', 'util', 'net', 'tls',
|
'fs', 'path', 'os', 'events', 'stream', 'util', 'net', 'tls',
|
||||||
|
|
@ -21,7 +26,8 @@ const nodeBuiltins = new Set([
|
||||||
// Packages that must be externalized because they break when bundled
|
// Packages that must be externalized because they break when bundled
|
||||||
// (fastify ecosystem uses internal file resolution that doesn't survive bundling)
|
// (fastify ecosystem uses internal file resolution that doesn't survive bundling)
|
||||||
const externalPackages = [
|
const externalPackages = [
|
||||||
'fastify', '@fastify/cors', '@fastify/static'
|
'fastify', '@fastify/cors', '@fastify/static',
|
||||||
|
'agent-teams-controller'
|
||||||
]
|
]
|
||||||
|
|
||||||
// Stub native .node addons (ssh2/cpu-features have JS fallbacks)
|
// Stub native .node addons (ssh2/cpu-features have JS fallbacks)
|
||||||
|
|
@ -57,6 +63,8 @@ export const ipcMain = { handle: noop, on: noop, removeHandler: noop };
|
||||||
export const shell = { openPath: noop, openExternal: noop };
|
export const shell = { openPath: noop, openExternal: noop };
|
||||||
export const dialog = { showOpenDialog: async () => ({ canceled: true, filePaths: [] }) };
|
export const dialog = { showOpenDialog: async () => ({ canceled: true, filePaths: [] }) };
|
||||||
export const Notification = class { show() {} };
|
export const Notification = class { show() {} };
|
||||||
|
export const safeStorage = { isEncryptionAvailable: () => false, encryptString: noop, decryptString: () => '' };
|
||||||
|
export const screen = proxyObj;
|
||||||
export default proxyObj;
|
export default proxyObj;
|
||||||
`
|
`
|
||||||
return {
|
return {
|
||||||
|
|
@ -75,12 +83,13 @@ export default proxyObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
root: ROOT,
|
||||||
plugins: [nativeModuleStub(), electronStub()],
|
plugins: [nativeModuleStub(), electronStub()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@main': resolve(__dirname, 'src/main'),
|
'@main': resolve(ROOT, 'src/main'),
|
||||||
'@shared': resolve(__dirname, 'src/shared'),
|
'@shared': resolve(ROOT, 'src/shared'),
|
||||||
'@preload': resolve(__dirname, 'src/preload')
|
'@preload': resolve(ROOT, 'src/preload')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ssr: {
|
ssr: {
|
||||||
|
|
@ -94,7 +103,7 @@ export default defineConfig({
|
||||||
ssr: true,
|
ssr: true,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
index: resolve(__dirname, 'src/main/standalone.ts')
|
index: resolve(ROOT, 'src/main/standalone.ts')
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@
|
||||||
* so that Sentry captures errors from the earliest point possible.
|
* so that Sentry captures errors from the earliest point possible.
|
||||||
*
|
*
|
||||||
* When `SENTRY_DSN` is not set (dev / self-builds), everything is a no-op.
|
* When `SENTRY_DSN` is not set (dev / self-builds), everything is a no-op.
|
||||||
|
*
|
||||||
|
* The @sentry/electron/main import is lazy so this module can be safely
|
||||||
|
* loaded in standalone (non-Electron) mode without crashing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Sentry from '@sentry/electron/main';
|
|
||||||
import {
|
import {
|
||||||
isValidDsn,
|
isValidDsn,
|
||||||
SENTRY_ENVIRONMENT,
|
SENTRY_ENVIRONMENT,
|
||||||
|
|
@ -34,25 +36,40 @@ export function syncTelemetryFlag(enabled: boolean): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Init
|
// Lazy Sentry import — safe in non-Electron environments
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const dsn = process.env.SENTRY_DSN;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let Sentry: any = null;
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
if (isValidDsn(dsn)) {
|
const dsn = process.env.SENTRY_DSN;
|
||||||
Sentry.init({
|
|
||||||
dsn,
|
|
||||||
release: SENTRY_RELEASE,
|
|
||||||
environment: SENTRY_ENVIRONMENT,
|
|
||||||
tracesSampleRate: TRACES_SAMPLE_RATE,
|
|
||||||
sendDefaultPii: false,
|
|
||||||
|
|
||||||
beforeSend(event) {
|
if (isValidDsn(dsn)) {
|
||||||
return telemetryAllowed ? event : null;
|
try {
|
||||||
},
|
// Dynamic import would be cleaner but top-level await is not available
|
||||||
});
|
// in all contexts. require() is synchronous and works in both Electron
|
||||||
initialized = true;
|
// and Node.js — it simply throws in standalone mode where the electron
|
||||||
|
// module is not resolvable.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
|
Sentry = require('@sentry/electron/main');
|
||||||
|
Sentry.init({
|
||||||
|
dsn,
|
||||||
|
release: SENTRY_RELEASE,
|
||||||
|
environment: SENTRY_ENVIRONMENT,
|
||||||
|
tracesSampleRate: TRACES_SAMPLE_RATE,
|
||||||
|
sendDefaultPii: false,
|
||||||
|
|
||||||
|
beforeSend(event: unknown) {
|
||||||
|
return telemetryAllowed ? event : null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
initialized = true;
|
||||||
|
} catch {
|
||||||
|
// @sentry/electron/main requires Electron runtime — not available in
|
||||||
|
// standalone (pure Node.js) mode. All exported helpers are no-ops when
|
||||||
|
// initialized is false, so this is safe to swallow.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue