Skip to content
Open
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
3 changes: 2 additions & 1 deletion .idea/biome.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions packages/app/src/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Inconsistent alias may break module resolution

All other intra-package imports use the #root/src/* alias, while this line points to #root/client/utils/env.server.ts.
If the file truly lives under packages/app/src/utils, this will fail at build time (or silently fall back to a second copy of Config, causing two singleton instances).

Run once to verify which path actually exists:

If only src/utils/env.server.ts is returned, fix the import:

-import { Config } from '#root/client/utils/env.server.ts'
+import { Config } from '#root/src/utils/env.server.ts'

🏁 Script executed:

#!/bin/bash
fd --max-depth 4 env.server.ts packages/app | sort

Length of output: 89


Fix inconsistent alias in env.server import

The import in packages/app/src/root.tsx line 22 uses the wrong alias and will fail to resolve:

- import { Config } from '#root/client/utils/env.server.ts'
+ import { Config } from '#root/src/utils/env.server.ts'

– packages/app/src/root.tsx:22

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Config } from '#root/client/utils/env.server.ts'
import { Config } from '#root/src/utils/env.server.ts'
🤖 Prompt for AI Agents (early access)
In packages/app/src/root.tsx at line 22, the import statement for Config uses
the incorrect alias '#root/client/utils/env.server.ts'. To fix this, update the
import path to use the consistent alias '#root/src/utils/env.server.ts' that
matches the project's aliasing convention and points to the correct file
location.

import { getDomainUrl } from '#root/src/utils/misc.ts'
import { getTheme } from '#root/src/utils/theme.server.ts'

Expand Down Expand Up @@ -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),
Expand Down
110 changes: 72 additions & 38 deletions packages/app/src/utils/env.server.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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',
Expand All @@ -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<typeof envSchema> {}
export class Config {
static #instance: Config
#env: Schema.Schema.Type<typeof envSchema>

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: <explanation>
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<typeof getEnv>
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
}
}
Loading