Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/release-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
build:
name: Build - macOS (${{ matrix.arch }})
runs-on: macos-latest
environment: production

strategy:
fail-fast: false
Expand Down Expand Up @@ -54,6 +55,9 @@ jobs:

- name: Compile app with electron-vite
working-directory: apps/desktop
env:
NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
run: bun run compile:app

# Build the Electron app for macOS
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop/electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,17 @@ export default defineConfig({

renderer: {
define: {
// Core env vars - Vite replaces these at build time
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
"process.platform": JSON.stringify(process.platform),
// API URLs - available in renderer if needed
"process.env.NEXT_PUBLIC_API_URL": JSON.stringify(
process.env.NEXT_PUBLIC_API_URL || "https://api.superset.sh",
),
"process.env.NEXT_PUBLIC_WEB_URL": JSON.stringify(
process.env.NEXT_PUBLIC_WEB_URL || "https://app.superset.sh",
),
// Custom env vars
"import.meta.env.DEV_SERVER_PORT": JSON.stringify(DEV_SERVER_PORT),
"import.meta.env.NEXT_PUBLIC_POSTHOG_KEY": JSON.stringify(
process.env.NEXT_PUBLIC_POSTHOG_KEY,
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/electron-app/factories/app/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
installExtension,
REACT_DEVELOPER_TOOLS,
} from "electron-extension-installer";
import { env } from "main/env.main";
import { PLATFORM } from "shared/constants";
import { makeAppId } from "shared/utils";
import { env } from "../../../../env";
import { ignoreConsoleWarnings } from "../../utils/ignore-console-warnings";

ignoreConsoleWarnings(["Manifest version 2 is deprecated"]);
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/electron-router-dom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BrowserWindow } from "electron";
import { createElectronRouter } from "electron-router-dom";
import { PORTS } from "shared/constants";
import { env } from "../env";
import { env } from "shared/env.shared";

const electronRouter = createElectronRouter({
port: PORTS.VITE_DEV_SERVER,
Expand Down
11 changes: 9 additions & 2 deletions apps/desktop/src/env.ts → apps/desktop/src/main/env.main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/**
* Environment variables for the MAIN PROCESS (Node.js context).
*
* This file uses t3-env with process.env which works at runtime in Node.js.
* Only import this file in src/main/ code - never in renderer or shared code.
*
* For renderer process env vars, use src/renderer/env.renderer.ts instead.
*/
Comment thread
coderabbitai[bot] marked this conversation as resolved.
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod/v4";

Expand All @@ -12,11 +20,10 @@ export const env = createEnv({

runtimeEnv: {
...process.env,
// Vite's define replaces this at build time, ensuring correct env in packaged apps
NODE_ENV: process.env.NODE_ENV,
},
emptyStringAsUndefined: true,

// Electron runs in a trusted environment - treat renderer as server context
// Main process runs in trusted Node.js environment
isServer: true,
});
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/api-client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { AppRouter } from "@superset/trpc";
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import { env } from "main/env.main";
import superjson from "superjson";
import { env } from "../../env";
import { authService } from "./auth";

/**
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/auth/auth-service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { EventEmitter } from "node:events";
import { TOKEN_CONFIG } from "@superset/shared/constants";
import { type BrowserWindow, shell } from "electron";
import { env } from "main/env.main";
import type { AuthProvider, AuthSession, SignInResult } from "shared/auth";
import { env } from "../../../env";
import { pkceStore } from "./pkce";
import { tokenStorage } from "./token-storage";

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/auth/deep-link-handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { env } from "main/env.main";
import type { AuthSession } from "shared/auth";
import { PROTOCOL_SCHEMES } from "shared/constants";
import { env } from "../../../env";
import { pkceStore } from "./pkce";

/**
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/auto-updater.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { app, type BrowserWindow, dialog } from "electron";
import { autoUpdater } from "electron-updater";
import { env } from "main/env.main";
import { PLATFORM } from "shared/constants";
import { env } from "../../env";

const UPDATE_CHECK_INTERVAL_MS = 1000 * 60 * 60 * 4; // 4 hours
const UPDATE_FEED_URL =
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/sound-paths.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { app } from "electron";
import { env } from "../../env";
import { env } from "main/env.main";

/**
* Gets the path to a ringtone sound file.
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/terminal-history.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createWriteStream, promises as fs, type WriteStream } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { env } from "../../env";
import { env } from "main/env.main";
import { SUPERSET_HOME_DIR } from "./app-environment";

export interface SessionMetadata {
Expand Down
43 changes: 43 additions & 0 deletions apps/desktop/src/renderer/env.renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Environment variables for the RENDERER PROCESS (browser context).
*
* These values are injected at BUILD TIME by Vite's `define` in electron.vite.config.ts.
* They are NOT read from process.env at runtime - Vite replaces the references with
* literal strings during compilation.
*
* Only import this file in src/renderer/ code - never in main or shared code.
*
* For main process env vars, use src/main/env.main.ts instead.
*/
import { z } from "zod/v4";

const envSchema = z.object({
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
NEXT_PUBLIC_API_URL: z.url().default("https://api.superset.sh"),
NEXT_PUBLIC_WEB_URL: z.url().default("https://app.superset.sh"),
NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(),
NEXT_PUBLIC_POSTHOG_HOST: z.string().default("https://us.i.posthog.com"),
});

/**
* Build-time environment variables.
*
* Vite replaces these process.env.* and import.meta.env.* references at build time.
* The values are baked into the bundle as string literals.
*/
const rawEnv = {
// These are replaced by Vite's define at build time
NODE_ENV: process.env.NODE_ENV,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL,
NEXT_PUBLIC_POSTHOG_KEY: import.meta.env.NEXT_PUBLIC_POSTHOG_KEY as
| string
| undefined,
NEXT_PUBLIC_POSTHOG_HOST: import.meta.env.NEXT_PUBLIC_POSTHOG_HOST as
| string
| undefined,
};

export const env = envSchema.parse(rawEnv);
12 changes: 4 additions & 8 deletions apps/desktop/src/renderer/lib/posthog.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import posthog from "posthog-js/dist/module.full.no-external";

const POSTHOG_KEY = import.meta.env.NEXT_PUBLIC_POSTHOG_KEY as
| string
| undefined;
import { env } from "../env.renderer";

export function initPostHog() {
if (!POSTHOG_KEY) {
if (!env.NEXT_PUBLIC_POSTHOG_KEY) {
console.log("[posthog] No key configured, skipping");
return;
}

posthog.init(POSTHOG_KEY, {
api_host: "https://us.i.posthog.com",
ui_host: "https://us.posthog.com",
posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: env.NEXT_PUBLIC_POSTHOG_HOST,
defaults: "2025-11-30",
capture_pageview: false,
capture_pageleave: false,
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/shared/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { env } from "../env";
import { env } from "./env.shared";

export const PLATFORM = {
IS_MAC: process.platform === "darwin",
Expand Down
30 changes: 30 additions & 0 deletions apps/desktop/src/shared/env.shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Environment variables safe for SHARED CODE (main + renderer).
*
* This file only accesses individual process.env properties that are:
* 1. Defined in Vite's `define` block (replaced at build time for renderer)
* 2. Available in main process via actual process.env
*
* DO NOT spread ...process.env here - that only works in main process.
*
* For main-process-only env vars (API URLs, etc.), use src/main/env.main.ts
* For renderer-only env vars (PostHog, etc.), use src/renderer/env.renderer.ts
*/
import { z } from "zod/v4";

const envSchema = z.object({
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
});

/**
* Shared environment variables.
*
* These work in both main and renderer because Vite's `define` replaces
* process.env.NODE_ENV at build time for renderer, while main process
* reads the actual value at runtime.
*/
export const env = envSchema.parse({
NODE_ENV: process.env.NODE_ENV,
});