feat(sentry): embed Sentry DSN at build time for packaged apps (#1025)
* feat(sentry): integrate Sentry configuration into Electron build - Added build-time constants for Sentry DSN and sampling rates in electron.vite.config.ts. - Enhanced environment variable handling in env-utils.ts to include Sentry settings for subprocesses. - Implemented getSentryEnvForSubprocess function in sentry.ts to provide Sentry environment variables for Python backends. - Updated Sentry-related functions to prioritize build-time constants over runtime environment variables for improved reliability. This integration ensures that Sentry is properly configured for both local development and CI environments. * fix(sentry): add typeof guards for build-time constants in tests The __SENTRY_*__ constants are only defined when Vite's define plugin runs during build. In test environments (vitest), these constants are undefined and cause ReferenceError. Added typeof guards to safely handle both cases. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,24 @@ import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { resolve } from 'path';
|
||||
|
||||
/**
|
||||
* Sentry configuration embedded at build time.
|
||||
*
|
||||
* In CI builds, these come from GitHub secrets.
|
||||
* In local development, these come from apps/frontend/.env (loaded by dotenv).
|
||||
*
|
||||
* The `define` option replaces these values at build time, so they're
|
||||
* embedded in the bundle and available at runtime in packaged apps.
|
||||
*/
|
||||
const sentryDefines = {
|
||||
'__SENTRY_DSN__': JSON.stringify(process.env.SENTRY_DSN || ''),
|
||||
'__SENTRY_TRACES_SAMPLE_RATE__': JSON.stringify(process.env.SENTRY_TRACES_SAMPLE_RATE || '0.1'),
|
||||
'__SENTRY_PROFILES_SAMPLE_RATE__': JSON.stringify(process.env.SENTRY_PROFILES_SAMPLE_RATE || '0.1'),
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
define: sentryDefines,
|
||||
plugins: [externalizeDepsPlugin({
|
||||
// Bundle these packages into the main process (they won't be in node_modules in packaged app)
|
||||
exclude: [
|
||||
@@ -43,6 +59,7 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
define: sentryDefines,
|
||||
root: resolve(__dirname, 'src/renderer'),
|
||||
build: {
|
||||
rollupOptions: {
|
||||
|
||||
@@ -15,6 +15,7 @@ import * as fs from 'fs';
|
||||
import { promises as fsPromises } from 'fs';
|
||||
import { execFileSync, execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { getSentryEnvForSubprocess } from './sentry';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
@@ -237,6 +238,11 @@ export function getAugmentedEnv(additionalPaths?: string[]): Record<string, stri
|
||||
// Prepend new paths to PATH (prepend so they take priority)
|
||||
env.PATH = [...pathsToAdd, currentPath].filter(Boolean).join(pathSeparator);
|
||||
|
||||
// Add Sentry environment variables for Python subprocesses
|
||||
// These are embedded at build time and need to be passed explicitly
|
||||
const sentryEnv = getSentryEnvForSubprocess();
|
||||
Object.assign(env, sentryEnv);
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
@@ -397,6 +403,11 @@ export async function getAugmentedEnvAsync(additionalPaths?: string[]): Promise<
|
||||
// Prepend new paths to PATH (prepend so they take priority)
|
||||
env.PATH = [...pathsToAdd, currentPath].filter(Boolean).join(pathSeparator);
|
||||
|
||||
// Add Sentry environment variables for Python subprocesses
|
||||
// These are embedded at build time and need to be passed explicitly
|
||||
const sentryEnv = getSentryEnvForSubprocess();
|
||||
Object.assign(env, sentryEnv);
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,34 +23,45 @@ import {
|
||||
type SentryErrorEvent
|
||||
} from '../shared/utils/sentry-privacy';
|
||||
|
||||
/**
|
||||
* Build-time constants defined in electron.vite.config.ts
|
||||
* These are replaced at build time with actual values from environment variables.
|
||||
* In development, they come from .env file. In CI builds, from GitHub secrets.
|
||||
*/
|
||||
declare const __SENTRY_DSN__: string;
|
||||
declare const __SENTRY_TRACES_SAMPLE_RATE__: string;
|
||||
declare const __SENTRY_PROFILES_SAMPLE_RATE__: string;
|
||||
|
||||
// In-memory state for current setting (updated via IPC when user toggles)
|
||||
let sentryEnabledState = true;
|
||||
|
||||
/**
|
||||
* Get Sentry DSN from environment variable
|
||||
* Get Sentry DSN from build-time constant
|
||||
*
|
||||
* For local development/testing:
|
||||
* - Add SENTRY_DSN to your .env file, or
|
||||
* - Run: SENTRY_DSN=your-dsn npm start
|
||||
*
|
||||
* For CI/CD releases:
|
||||
* - Set SENTRY_DSN as a GitHub Actions secret
|
||||
*
|
||||
* For forks:
|
||||
* - Without SENTRY_DSN, Sentry is disabled (safe for forks)
|
||||
* The DSN is embedded at build time via Vite's `define` option.
|
||||
* - In local development: comes from .env file (loaded by dotenv)
|
||||
* - In CI builds: comes from GitHub secrets
|
||||
* - For forks: without SENTRY_DSN, Sentry is disabled (safe for forks)
|
||||
*/
|
||||
function getSentryDsn(): string {
|
||||
return process.env.SENTRY_DSN || '';
|
||||
// __SENTRY_DSN__ is replaced at build time with the actual value
|
||||
// Falls back to runtime env var for development flexibility
|
||||
// typeof guard needed for test environments where Vite's define doesn't apply
|
||||
const buildTimeValue = typeof __SENTRY_DSN__ !== 'undefined' ? __SENTRY_DSN__ : '';
|
||||
return buildTimeValue || process.env.SENTRY_DSN || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trace sample rate from environment variable
|
||||
* Get trace sample rate from build-time constant
|
||||
* Controls performance monitoring sampling (0.0 to 1.0)
|
||||
* Default: 0.1 (10%) in production, 0 in development
|
||||
*/
|
||||
function getTracesSampleRate(): number {
|
||||
const envValue = process.env.SENTRY_TRACES_SAMPLE_RATE;
|
||||
if (envValue !== undefined) {
|
||||
// Try build-time constant first, then runtime env var
|
||||
// typeof guard needed for test environments where Vite's define doesn't apply
|
||||
const buildTimeValue = typeof __SENTRY_TRACES_SAMPLE_RATE__ !== 'undefined' ? __SENTRY_TRACES_SAMPLE_RATE__ : '';
|
||||
const envValue = buildTimeValue || process.env.SENTRY_TRACES_SAMPLE_RATE;
|
||||
if (envValue) {
|
||||
const parsed = parseFloat(envValue);
|
||||
if (!isNaN(parsed) && parsed >= 0 && parsed <= 1) {
|
||||
return parsed;
|
||||
@@ -61,13 +72,16 @@ function getTracesSampleRate(): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get profile sample rate from environment variable
|
||||
* Get profile sample rate from build-time constant
|
||||
* Controls profiling sampling relative to traces (0.0 to 1.0)
|
||||
* Default: 0.1 (10%) in production, 0 in development
|
||||
*/
|
||||
function getProfilesSampleRate(): number {
|
||||
const envValue = process.env.SENTRY_PROFILES_SAMPLE_RATE;
|
||||
if (envValue !== undefined) {
|
||||
// Try build-time constant first, then runtime env var
|
||||
// typeof guard needed for test environments where Vite's define doesn't apply
|
||||
const buildTimeValue = typeof __SENTRY_PROFILES_SAMPLE_RATE__ !== 'undefined' ? __SENTRY_PROFILES_SAMPLE_RATE__ : '';
|
||||
const envValue = buildTimeValue || process.env.SENTRY_PROFILES_SAMPLE_RATE;
|
||||
if (envValue) {
|
||||
const parsed = parseFloat(envValue);
|
||||
if (!isNaN(parsed) && parsed >= 0 && parsed <= 1) {
|
||||
return parsed;
|
||||
@@ -165,3 +179,28 @@ export function setSentryEnabled(enabled: boolean): void {
|
||||
sentryEnabledState = enabled;
|
||||
console.log(`[Sentry] Error reporting ${enabled ? 'enabled' : 'disabled'} (programmatic)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Sentry environment variables for passing to Python subprocesses
|
||||
*
|
||||
* This returns the build-time embedded values so that Python backends
|
||||
* can also report errors to Sentry in packaged apps.
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
* const env = { ...getAugmentedEnv(), ...getSentryEnvForSubprocess() };
|
||||
* spawn(pythonPath, args, { env });
|
||||
* ```
|
||||
*/
|
||||
export function getSentryEnvForSubprocess(): Record<string, string> {
|
||||
const dsn = getSentryDsn();
|
||||
if (!dsn) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
SENTRY_DSN: dsn,
|
||||
SENTRY_TRACES_SAMPLE_RATE: String(getTracesSampleRate()),
|
||||
SENTRY_PROFILES_SAMPLE_RATE: String(getProfilesSampleRate()),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user