diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0503f4fae..875b6a522 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,6 @@ env: VITE_IMGPROXY_URL: ${{ vars.VITE_IMGPROXY_URL }} VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} VITE_POSTHOG_KEY: ${{ vars.VITE_POSTHOG_KEY }} - VITE_BUILD_TYPE: internal jobs: release: diff --git a/electron.vite.config.ts b/electron.vite.config.ts index f7b6d9a6c..fd1912e31 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -12,16 +12,24 @@ export default defineConfig({ resolve: { alias: { "@shared": resolve("src/shared/src"), + "@env": resolve("./src/env.ts"), + }, + }, + }, + preload: { + resolve: { + alias: { + "@env": resolve("./src/env.ts"), }, }, }, - preload: {}, renderer: { resolve: { alias: { "@renderer": resolve("src/renderer/src"), "@shared": resolve("src/shared/src"), "@pkg": resolve("./package.json"), + "@env": resolve("./src/env.ts"), }, }, plugins: [ diff --git a/package.json b/package.json index 660bdb4fa..fc8803915 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@sentry/react": "8.9.2", "@sentry/vite-plugin": "2.20.1", "@shikijs/transformers": "1.10.0", + "@t3-oss/env-core": "^0.10.1", "@tanstack/query-sync-storage-persister": "5.49.0", "@tanstack/react-query": "5.49.0", "@tanstack/react-query-persist-client": "5.49.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 653e1d290..7e0316a58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,6 +103,9 @@ importers: '@shikijs/transformers': specifier: 1.10.0 version: 1.10.0 + '@t3-oss/env-core': + specifier: ^0.10.1 + version: 0.10.1(typescript@5.5.2)(zod@3.23.8) '@tanstack/query-sync-storage-persister': specifier: 5.49.0 version: 5.49.0 @@ -2368,6 +2371,15 @@ packages: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} + '@t3-oss/env-core@0.10.1': + resolution: {integrity: sha512-GcKZiCfWks5CTxhezn9k5zWX3sMDIYf6Kaxy2Gx9YEQftFcz8hDRN56hcbylyAO3t4jQnQ5ifLawINsNgCDpOg==} + peerDependencies: + typescript: '>=5.0.0' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@tailwindcss/container-queries@0.1.1': resolution: {integrity: sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==} peerDependencies: @@ -9163,6 +9175,12 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@t3-oss/env-core@0.10.1(typescript@5.5.2)(zod@3.23.8)': + dependencies: + zod: 3.23.8 + optionalDependencies: + typescript: 5.5.2 + '@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.4)': dependencies: tailwindcss: 3.4.4 diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 000000000..321669c94 --- /dev/null +++ b/src/env.ts @@ -0,0 +1,18 @@ +import { createEnv } from "@t3-oss/env-core" +import { z } from "zod" + +const isDev = "process" in globalThis ? process.env.NODE_ENV === "development" : import.meta.env.DEV +export const env = createEnv({ + clientPrefix: "VITE_", + client: { + VITE_WEB_URL: z.string().url(), + VITE_API_URL: z.string().url(), + VITE_IMGPROXY_URL: z.string().url(), + VITE_SENTRY_DSN: z.string().optional(), + VITE_POSTHOG_KEY: z.string().optional(), + }, + + emptyStringAsUndefined: true, + runtimeEnv: "process" in globalThis ? process.env : import.meta.env, + skipValidation: !isDev, +}) diff --git a/src/main/config.ts b/src/main/config.ts deleted file mode 100644 index 8b62f363e..000000000 --- a/src/main/config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { z } from "zod" - -export const ReleaseTypeSchema = z.enum([ - "stable", - "beta", - "internal", -]) - -export const envBuildType = (process.env.VITE_BUILD_TYPE || "internal") - .trim() - .toLowerCase() - -export const buildType = ReleaseTypeSchema.parse(envBuildType) - -export const mode = process.env.NODE_ENV -export const isDev = mode === "development" diff --git a/src/main/env.ts b/src/main/env.ts new file mode 100644 index 000000000..62d226135 --- /dev/null +++ b/src/main/env.ts @@ -0,0 +1,7 @@ +import { app } from "electron" +import * as semver from "semver" + +export const mode = process.env.NODE_ENV +export const isDev = mode === "development" + +export const channel = isDev ? "development" : ((semver.prerelease(app.getVersion())?.[0] as string) || "stable") diff --git a/src/main/index.ts b/src/main/index.ts index 4b9482632..e7054616a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -7,10 +7,13 @@ import { APP_PROTOCOL, DEEPLINK_SCHEME } from "@shared/constants" import { extractElectronWindowOptions } from "@shared/electron" import { app, BrowserWindow, session } from "electron" +import { env } from "../env" +import { isDev } from "./env" import { initializationApp } from "./init" import { registerUpdater } from "./updater" import { createMainWindow, createWindow } from "./window" +if (isDev) console.info("[main] env loaded:", env) initializationApp() let mainWindow: BrowserWindow diff --git a/src/main/updater/index.ts b/src/main/updater/index.ts index 1b763120d..292ec15b7 100644 --- a/src/main/updater/index.ts +++ b/src/main/updater/index.ts @@ -1,7 +1,7 @@ import { getRendererHandlers } from "@egoist/tipc/main" import { autoUpdater } from "electron-updater" -import { buildType, isDev } from "../config" +import { channel, isDev } from "../env" import { logger } from "../logger" import type { RendererHandlers } from "../renderer-handlers" import { getMainWindow } from "../window" @@ -63,17 +63,17 @@ export const registerUpdater = async () => { const allowAutoUpdate = true autoUpdater.autoDownload = false - autoUpdater.allowPrerelease = buildType !== "stable" + autoUpdater.allowPrerelease = channel !== "stable" autoUpdater.autoInstallOnAppQuit = true autoUpdater.autoRunAppAfterInstall = true const feedUrl: Exclude[0], string> = { - channel: buildType, + channel, // hack for custom provider provider: "custom" as "github", repo: "follow", owner: "RSSNext", - releaseType: buildType === "stable" ? "release" : "prerelease", + releaseType: channel === "stable" ? "release" : "prerelease", // @ts-expect-error hack for custom provider updateProvider: CustomGitHubProvider, } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index e417cbb76..c10b61276 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -2,6 +2,7 @@ import { queryClient } from "@renderer/lib/query-client" import { useEffect, useLayoutEffect } from "react" import { Outlet } from "react-router-dom" +import { env } from "../../env.js" import { useAppIsReady } from "./atoms/app" import { useUISettingKey } from "./atoms/settings/ui" import { Logo } from "./components/icons/logo" @@ -11,6 +12,9 @@ import { getOS } from "./lib/utils" import { RootProviders } from "./providers/root-providers" import { handlers } from "./tipc" +if (import.meta.env.DEV) { + console.info("[renderer] env loaded:", env) +} function App() { useEffect(() => { const cleanup = handlers?.invalidateQuery.listen((queryKey) => { diff --git a/src/renderer/src/configs.ts b/src/renderer/src/configs.ts index 4d3e66f9c..4f78ead66 100644 --- a/src/renderer/src/configs.ts +++ b/src/renderer/src/configs.ts @@ -1,3 +1,4 @@ +import { env } from "@env" import type { BrowserOptions } from "@sentry/react" import { FetchError } from "ofetch" @@ -5,7 +6,7 @@ export const SentryConfig: BrowserOptions = { // Performance Monitoring tracesSampleRate: 1, // Capture 100% of the transactions // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled - tracePropagationTargets: ["localhost", import.meta.env.VITE_API_URL], + tracePropagationTargets: ["localhost", env.VITE_API_URL], // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1, diff --git a/src/renderer/src/initialize/index.ts b/src/renderer/src/initialize/index.ts index 0ed07e1f1..c5395a4ff 100644 --- a/src/renderer/src/initialize/index.ts +++ b/src/renderer/src/initialize/index.ts @@ -1,3 +1,4 @@ +import { env } from "@env" import { authConfigManager } from "@hono/auth-js/react" import { repository } from "@pkg" import { browserDB } from "@renderer/database" @@ -65,7 +66,7 @@ export const initializeApp = async () => { // Initialize the auth config authConfigManager.setConfig({ - baseUrl: import.meta.env.VITE_API_URL, + baseUrl: env.VITE_API_URL, basePath: "/auth", credentials: "include", }) diff --git a/src/renderer/src/initialize/posthog.ts b/src/renderer/src/initialize/posthog.ts index bba8f7e44..c8e667812 100644 --- a/src/renderer/src/initialize/posthog.ts +++ b/src/renderer/src/initialize/posthog.ts @@ -1,3 +1,4 @@ +import { env } from "@env" import { getUser } from "@renderer/atoms/user" declare global { @@ -9,7 +10,9 @@ declare global { export const initPostHog = async () => { if (import.meta.env.DEV) return const { default: posthog } = await import("posthog-js") - posthog.init(import.meta.env.VITE_POSTHOG_KEY, { + + if (env.VITE_POSTHOG_KEY === undefined) return + posthog.init(env.VITE_POSTHOG_KEY, { person_profiles: "identified_only", }) diff --git a/src/renderer/src/initialize/sentry.ts b/src/renderer/src/initialize/sentry.ts index aadfea8ee..fd131b858 100644 --- a/src/renderer/src/initialize/sentry.ts +++ b/src/renderer/src/initialize/sentry.ts @@ -1,4 +1,6 @@ +import { env } from "@env" import { version } from "@pkg" +import { channel } from "@renderer/lib/constants" import { useEffect } from "react" import { createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from "react-router-dom" @@ -11,8 +13,8 @@ export const initSentry = async () => { import("posthog-js").then((module) => module.default), ]) Sentry.init({ - dsn: import.meta.env.VITE_SENTRY_DSN, - environment: import.meta.env.VITE_BUILD_TYPE ?? "internal", + dsn: env.VITE_SENTRY_DSN, + environment: channel, integrations: [ Sentry.moduleMetadataIntegration(), Sentry.reactRouterV6BrowserTracingIntegration({ diff --git a/src/renderer/src/lib/constants.tsx b/src/renderer/src/lib/constants.tsx index 5e4e2631b..669039af6 100644 --- a/src/renderer/src/lib/constants.tsx +++ b/src/renderer/src/lib/constants.tsx @@ -1,3 +1,5 @@ +import * as semver from "semver" + import { getStorageNS } from "./ns" export const levels = { @@ -83,3 +85,5 @@ export const QUERY_PERSIST_KEY = getStorageNS("REACT_QUERY_OFFLINE_CACHE") export const ROUTE_FEED_PENDING = "all" export const ROUTE_ENTRY_PENDING = "pending" export const ROUTE_FEED_IN_FOLDER = "folder-" + +export const channel = import.meta.env.DEV ? "development" : ((semver.prerelease(APP_VERSION)?.[0] as string) || "stable") diff --git a/src/renderer/src/lib/img-proxy.ts b/src/renderer/src/lib/img-proxy.ts index 31c254e51..2cb8cba48 100644 --- a/src/renderer/src/lib/img-proxy.ts +++ b/src/renderer/src/lib/img-proxy.ts @@ -1,3 +1,5 @@ +import { env } from "@env" + export const getProxyUrl = ({ url, width, @@ -6,4 +8,4 @@ export const getProxyUrl = ({ url: string width: number height: number -}) => `${import.meta.env.VITE_IMGPROXY_URL}/unsafe/${width}x${height}/${encodeURIComponent(url)}` +}) => `${env.VITE_IMGPROXY_URL}/unsafe/${width}x${height}/${encodeURIComponent(url)}` diff --git a/src/shared/src/constants.ts b/src/shared/src/constants.ts index 337237558..b0f84ba37 100644 --- a/src/shared/src/constants.ts +++ b/src/shared/src/constants.ts @@ -1,5 +1,7 @@ +import { env } from "@env" + export const APP_PROTOCOL = import.meta.env.DEV ? "follow-dev" : "follow" export const DEEPLINK_SCHEME = `${APP_PROTOCOL}://` // export const WEB_URL = import.meta.env.VITE_VERCEL_URL ?? import.meta.env.VITE_WEB_URL -export const WEB_URL = import.meta.env.VITE_WEB_URL +export const WEB_URL = env.VITE_WEB_URL diff --git a/tsconfig.node.json b/tsconfig.node.json index 7c9a55bc2..59ee8d6c2 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -5,17 +5,22 @@ "src/main/**/*", "src/preload/**/*", "types/**/*.d.ts", - "src/shared/src/**/*" + "src/shared/src/**/*", + "src/env.ts" ], "compilerOptions": { "composite": true, "types": [ "electron-vite/node" ], + "moduleResolution": "Bundler", "baseUrl": ".", "paths": { "@shared/*": [ "src/shared/src/*" + ], + "@env": [ + "./src/env.ts" ] } } diff --git a/tsconfig.web.json b/tsconfig.web.json index 4e3fef419..a16d2e4a2 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -6,11 +6,13 @@ "src/renderer/src/**/*.tsx", "src/preload/*.d.ts", "src/shared/src/**/*", - "types/**/*.d.ts" + "types/**/*.d.ts", + "src/env.ts" ], "compilerOptions": { "composite": true, "jsx": "react-jsx", + "moduleResolution": "Bundler", "target": "ES2022", "noUnusedLocals": false, "strict": true, @@ -25,7 +27,8 @@ "@shared/*": [ "src/shared/src/*" ], - "@pkg": ["./package.json"] + "@pkg": ["./package.json"], + "@env": ["./src/env.ts"] } } } diff --git a/types/vite.d.ts b/types/vite.d.ts index 3b89bc40b..beee6b7e9 100644 --- a/types/vite.d.ts +++ b/types/vite.d.ts @@ -6,7 +6,6 @@ interface ImportMetaEnv { VITE_API_URL: string VITE_IMGPROXY_URL: string VITE_SENTRY_DSN: string - VITE_BUILD_TYPE: string VITE_POSTHOG_KEY: string } diff --git a/vite.config.ts b/vite.config.ts index 3d34f408c..d17262d3a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,6 +20,7 @@ export default defineConfig({ "@renderer": resolve("src/renderer/src"), "@shared": resolve("src/shared/src"), "@pkg": resolve("./package.json"), + "@env": resolve("./src/env.ts"), }, }, base: "/",