diff --git a/next.config.mjs b/next.config.mjs index 811a2e2..8949b40 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,9 +2,10 @@ * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful * for Docker builds. */ -import './src/env.js' + import { createMDX } from 'fumadocs-mdx/next' import createNextIntlPlugin from 'next-intl/plugin' +import './src/env.js' const withMDX = createMDX() diff --git a/src/app/api/neatqueue-webhook/route.ts b/src/app/api/neatqueue-webhook/route.ts index f48c594..ef9c3ee 100644 --- a/src/app/api/neatqueue-webhook/route.ts +++ b/src/app/api/neatqueue-webhook/route.ts @@ -1,11 +1,12 @@ import crypto from 'node:crypto' import { type NextRequest, NextResponse } from 'next/server' +import { env } from '@/env' import { globalEmitter } from '@/lib/events' import type { PlayerState } from '@/server/api/routers/player-state' import { PLAYER_STATE_KEY, redis } from '@/server/redis' import { leaderboardService } from '@/server/services/leaderboard' -const EXPECTED_QUERY_SECRET = process.env.WEBHOOK_QUERY_SECRET +const EXPECTED_QUERY_SECRET = env.WEBHOOK_QUERY_SECRET type WebhookPlayer = { id?: string | number diff --git a/src/app/docs/[[...slug]]/page.tsx b/src/app/docs/[[...slug]]/page.tsx index 880a566..fa9bf1a 100644 --- a/src/app/docs/[[...slug]]/page.tsx +++ b/src/app/docs/[[...slug]]/page.tsx @@ -19,6 +19,7 @@ import { Nemesis } from '@/app/_components/nemesis' import { Spectral } from '@/app/_components/spectral' import { Xmult } from '@/app/_components/xmult' import { Button } from '@/components/ui/button' +import { env } from '@/env' import { CDN_URL } from '@/shared/constants' import { metadataImage } from '../../../../lib/metadata' import { source } from '../../../../lib/source' @@ -73,8 +74,7 @@ export default async function Page(props: { ...defaultMdxComponents, img: (props: ImageZoomProps) => { const isDev = - process.env.NODE_ENV === 'development' || - process.env.IS_PREVIEW === 'true' + env.NODE_ENV === 'development' || env.IS_PREVIEW === 'true' if (isDev) { return } diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 50c0242..9c26760 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -3,6 +3,7 @@ import * as AvatarPrimitive from '@radix-ui/react-avatar' import type * as React from 'react' +import { env } from '@/env' import { cn } from '@/lib/utils' import { CDN_URL } from '@/shared/constants' @@ -27,7 +28,7 @@ function AvatarImage({ src, ...props }: React.ComponentProps) { - const isDev = process.env.NODE_ENV === 'development' + const isDev = env.NODE_ENV === 'development' let finalSrc = src if (typeof src === 'string' && src.startsWith('/') && !isDev) { finalSrc = `${CDN_URL}${src}` diff --git a/src/env.js b/src/env.js index 75527f8..91fd853 100644 --- a/src/env.js +++ b/src/env.js @@ -24,6 +24,16 @@ export const env = createEnv({ MINIO_BUCKET_NAME: z.string(), MINIO_LEADERBOARD_BUCKET_NAME: z.string(), MINIO_USE_SSL: z.enum(['true', 'false']).default('false'), + API_TOKEN: z.string(), + VERCEL_URL: z.string().optional(), + PORT: z.string().optional(), + IS_PREVIEW: z.string().optional(), + }, + + /** + * Shared variables available on both client and server (e.g. NODE_ENV). + */ + shared: { NODE_ENV: z .enum(['development', 'test', 'production']) .default('development'), @@ -59,6 +69,10 @@ export const env = createEnv({ MINIO_LEADERBOARD_BUCKET_NAME: process.env.MINIO_LEADERBOARD_BUCKET_NAME, MINIO_USE_SSL: process.env.MINIO_USE_SSL, NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + API_TOKEN: process.env.API_TOKEN, + VERCEL_URL: process.env.VERCEL_URL, + PORT: process.env.PORT, + IS_PREVIEW: process.env.IS_PREVIEW, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index b35648f..a341c63 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -11,6 +11,7 @@ import { initTRPC, TRPCError } from '@trpc/server' import superjson from 'superjson' import { ZodError } from 'zod' +import { env } from '@/env' import { auth } from '@/server/auth' import { db } from '@/server/db' @@ -58,7 +59,7 @@ const t = initTRPC.context().create({ } // Clear stack trace in production to prevent retention - if (process.env.NODE_ENV === 'production') { + if (env.NODE_ENV === 'production') { const { stack: _stack, ...safeFormatted } = formatted as typeof formatted & { stack?: unknown diff --git a/src/server/services/botlatro.service.ts b/src/server/services/botlatro.service.ts index 38904c3..0366281 100644 --- a/src/server/services/botlatro.service.ts +++ b/src/server/services/botlatro.service.ts @@ -1,4 +1,5 @@ import { eq } from 'drizzle-orm' +import { env } from '@/env' import { db } from '../db' import { transcripts } from '../db/schema' import { redis } from '../redis' @@ -13,7 +14,7 @@ export const botlatro_service = { get_active_matches: async (): Promise => { const response = await fetch(`${BOTLATRO_URL}api/matches/active-counts`, { headers: { - Authorization: `Bearer ${process.env.API_TOKEN}`, + Authorization: `Bearer ${env.API_TOKEN}`, }, }) if (!response.ok) { @@ -112,7 +113,7 @@ export const botlatro_service = { `${BOTLATRO_URL}api/transcripts/view/${gameNumber}`, { headers: { - Authorization: `Bearer ${process.env.API_TOKEN}`, + Authorization: `Bearer ${env.API_TOKEN}`, }, } ) diff --git a/src/trpc/react.tsx b/src/trpc/react.tsx index 72580c2..d930982 100644 --- a/src/trpc/react.tsx +++ b/src/trpc/react.tsx @@ -12,6 +12,7 @@ import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server' import { useState } from 'react' import SuperJSON from 'superjson' +import { env } from '@/env' import type { AppRouter } from '@/server/api/root' import { createQueryClient } from './query-client' @@ -51,7 +52,7 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) { links: [ loggerLink({ enabled: (op) => - process.env.NODE_ENV === 'development' || + env.NODE_ENV === 'development' || (op.direction === 'down' && op.result instanceof Error), }), @@ -87,9 +88,9 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) { } function getBaseUrl() { - if (process.env.NODE_ENV === 'production') return 'https://balatromp.com' + if (env.NODE_ENV === 'production') return 'https://balatromp.com' if (typeof window !== 'undefined') return window.location.origin - if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}` + if (env.VERCEL_URL) return `https://${env.VERCEL_URL}` - return `http://localhost:${process.env.PORT ?? 3000}` + return `http://localhost:${env.PORT ?? 3000}` } diff --git a/test.ts b/test.ts index 5d042f6..3b4aeb3 100644 --- a/test.ts +++ b/test.ts @@ -1,9 +1,10 @@ import { desc, eq } from 'drizzle-orm' import { createClient } from 'redis' +import { env } from '@/env' import { db } from '@/server/db' import { player_games } from '@/server/db/schema' -const redisClient = await createClient({ url: process.env.REDIS_URL }) +const redisClient = await createClient({ url: env.REDIS_URL }) .on('error', (err) => console.error('Redis Client Error', err)) .connect()