From c8842390c7d9803fe11a4c2fb043034c05a31cd1 Mon Sep 17 00:00:00 2001
From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com>
Date: Wed, 7 May 2025 18:48:18 +0200
Subject: [PATCH 1/2] chore(.idea): update biome and tailwindcss configurations
Updated biome settings to use manual configuration mode and set executable path correctly. Removed the Tailwind CSS LSP server package reference for cleanup.
---
.idea/biome.xml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.idea/biome.xml b/.idea/biome.xml
index aff3c1eb0..f64dbeeb8 100644
--- a/.idea/biome.xml
+++ b/.idea/biome.xml
@@ -3,8 +3,9 @@
+
-
+
From 4d9d928f1156e48837a7ebbc8c39775577b16bd2 Mon Sep 17 00:00:00 2001
From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com>
Date: Wed, 7 May 2025 18:48:55 +0200
Subject: [PATCH 2/2] refactor(app): replace env handling with Config class
Replaced `init` and `getEnv` functions with a singleton `Config`
class for environment variable management. Centralizes
initialization, improves encapsulation, and ensures separation of
server and client environment variables. Updated related imports and
usage throughout the app.
---
packages/app/src/entry.server.tsx | 5 +-
packages/app/src/root.tsx | 4 +-
packages/app/src/utils/env.server.ts | 110 ++++++++++++++++++---------
3 files changed, 76 insertions(+), 43 deletions(-)
diff --git a/packages/app/src/entry.server.tsx b/packages/app/src/entry.server.tsx
index 85e30c9e5..99d40afac 100644
--- a/packages/app/src/entry.server.tsx
+++ b/packages/app/src/entry.server.tsx
@@ -6,12 +6,11 @@ import { isbot } from 'isbot'
import { renderToPipeableStream } from 'react-dom/server'
import { type AppLoadContext, type EntryContext, ServerRouter } from 'react-router'
-import { getEnv, init } from '#root/src/utils/env.server.ts'
+import { Config } from '#root/src/utils/env.server.ts'
const ABORT_DELAY = 5_000
-init()
-global.ENV = getEnv()
+global.ENV = Config.instance.serverEnv
export default function handleRequest(
request: Request,
diff --git a/packages/app/src/root.tsx b/packages/app/src/root.tsx
index 7b603d9ba..800d2b764 100644
--- a/packages/app/src/root.tsx
+++ b/packages/app/src/root.tsx
@@ -19,7 +19,7 @@ import { useOptionalTheme, useTheme } from '#root/src/routes/resources/theme-swi
import { Footer, GeneralErrorBoundary, Header } from '#root/src/shell/index.tsx'
import tailwindStyleSheetUrl from '#root/src/styles/tailwind.css?url'
import { ClientHintCheck, getHints } from '#root/src/utils/client-hints.tsx'
-import { getEnv } from '#root/src/utils/env.server.ts'
+import { Config } from '#root/client/utils/env.server.ts'
import { getDomainUrl } from '#root/src/utils/misc.ts'
import { getTheme } from '#root/src/utils/theme.server.ts'
@@ -67,7 +67,7 @@ export function meta({ location }: Route.MetaArgs) {
export function loader({ request }: Route.LoaderArgs) {
return {
- ENV: getEnv(),
+ ENV: Config.instance.clientEnv,
requestInfo: {
hints: getHints(request),
diff --git a/packages/app/src/utils/env.server.ts b/packages/app/src/utils/env.server.ts
index 36d5f0c93..54a4d8ffa 100644
--- a/packages/app/src/utils/env.server.ts
+++ b/packages/app/src/utils/env.server.ts
@@ -1,7 +1,6 @@
import * as process from 'node:process'
-import { Either, Schema } from 'effect'
-import { TreeFormatter } from 'effect/ParseResult'
+import { Either, ParseResult, pipe, Schema } from 'effect'
const envSchema = Schema.Struct({
ALLOW_INDEXING: Schema.optionalWith(
@@ -27,6 +26,11 @@ const envSchema = Schema.Struct({
exact: true,
},
),
+
+ APP_ENV: Schema.optionalWith(Schema.Literal('development', 'staging', 'production'), {
+ default: () => 'development',
+ }),
+
GITHUB_TOKEN: Schema.optionalWith(
Schema.NonEmptyString.annotations({
description: 'GitHub token',
@@ -43,50 +47,80 @@ const envSchema = Schema.Struct({
NODE_ENV: Schema.Literal('production', 'development', 'test'),
})
-declare global {
- namespace NodeJS {
- interface ProcessEnv extends Schema.Schema.Encoded {}
+export class Config {
+ static #instance: Config
+ #env: Schema.Schema.Type
+
+ private constructor() {
+ this.#init()
}
-}
-/**
- *
- * Validates the environment variables.
- * If the environment variables are invalid, an error is thrown...
- * Invoke this function at the edge of your application to ensure that the environment variables are set.
- * Later make sure to use {@link getEnv} to access the environment variables.
- */
-export function init(): void {
- const maybeEnv = Schema.decodeUnknownEither(envSchema)(process.env, { errors: 'all' })
- if (Either.isLeft(maybeEnv)) {
- // biome-ignore lint/suspicious/noConsole:
- console.error('❌ Invalid environment variables:', TreeFormatter.formatError(maybeEnv.left))
- throw new Error('Invalid environment variables')
+ static get instance(): Config {
+ if (!Config.#instance) {
+ Config.#instance = new Config()
+ }
+
+ return Config.#instance
}
-}
-/**
- * This is used in both `entry.server.ts` and `root.tsx` to ensure that
- * the environment variables are set and globally available before the app is
- * started.
- *
- * NOTE: Do *not* add any environment variables in here that you do not wish to
- * be included in the src.
- * @returns all public ENV variables
- */
-
-export function getEnv() {
- return {
- ALLOW_INDEXING: process.env.ALLOW_INDEXING,
- MODE: process.env.NODE_ENV,
- } as const
+ /**
+ * Initialize the environment variables.
+ */
+ #init(): void {
+ pipe(
+ process.env,
+ Schema.decodeUnknownEither(envSchema, { errors: 'all' }),
+ Either.match({
+ onLeft: (parseError): void => {
+ console.error(
+ '❌ Invalid environment variables:',
+ ParseResult.TreeFormatter.formatErrorSync(parseError),
+ )
+ throw new Error('Invalid environment variables')
+ },
+ onRight: (env): void => {
+ this.#env = env
+ Object.freeze(this.#env)
+
+ // Do not log the message when running tests
+ if (this.#env.NODE_ENV !== 'test') {
+ // biome-ignore lint/suspicious/noConsole: We want this to be logged
+ console.log('✅ Environment variables loaded successfully')
+ }
+ },
+ }),
+ )
+ }
+
+ get serverEnv() {
+ return this.#env
+ }
+
+ /**
+ * Helper getter which returns a subset of the environment vars which are safe expose to the client.
+ * Don't expose any secrets or sensitive data here.
+ * Otherwise, you would expose your server vars to the client if you returned them from here as this is
+ * directly sent in the root to the client and set on the window.env
+ * @returns Subset of the whole process.env to be passed to the client and used there
+ */
+ get clientEnv() {
+ const serverEnv = this.serverEnv
+ return {
+ NODE_ENV: serverEnv.NODE_ENV,
+ }
+ }
}
-export type Env = ReturnType
+type ServerEnvVars = typeof Config.instance.serverEnv
+type ClientEnvVars = typeof Config.instance.clientEnv
declare global {
- var ENV: Env
+ namespace NodeJS {
+ interface ProcessEnv extends ServerEnvVars {}
+ }
+
+ var ENV: ClientEnvVars
interface Window {
- ENV: Env
+ ENV: ClientEnvVars
}
}