Skip to content

Commit

Permalink
chore: cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
denchiklut committed Sep 29, 2024
1 parent 8839f96 commit 52c8182
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 78 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ You can use .env file to specify environment variables. This file is ignored by
#### Adding new `env` variable

1. Add it to `.env` file
2. For TS completion and validation add it to `envSchema` in `src/common/env/env.util.ts`
2. For TS completion and validation add it to `envSchema` in `src/common/env/index.ts`
3. If this variable needs to be accessible from both `client` & `server` make sure it's name starts with prefix `CLIENT_`
4. You can access environment variable via `getENV` function.
This function will return a proper value based on environment (client/server) and cast it to a proper type based on `envSchema` from `step 2` (string/number/boolean)
Expand Down
4 changes: 2 additions & 2 deletions config/spec/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ window.IS_DEV = false
window.IS_SPA = true
window.clientPrefix = 'PUBLIC_'

jest.mock('src/common/env/create-env', () => ({
...jest.requireActual('src/common/env/create-env'),
jest.mock('src/common/env/env.util', () => ({
...jest.requireActual('src/common/env/env.util'),
createEnv: jest.fn(() => ({
CLIENT_HOST: 'http://localhost:3000',
CLIENT_PUBLIC_PATH: '/',
Expand Down
3 changes: 2 additions & 1 deletion src/client/components/@shared/error/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useRouteError } from 'react-router'
import { logger } from 'src/common'

export const Fallback = () => {
const error = useRouteError()
console.error(error)
logger.error(error)

return <p>Something went wrong</p>
}
42 changes: 0 additions & 42 deletions src/common/env/create-env.ts

This file was deleted.

62 changes: 33 additions & 29 deletions src/common/env/env.util.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
import { string, mixed, object, type InferType } from 'yup'
import { getOrDefault } from './get.util'
import { createEnv } from './create-env'
import type { ObjectSchema, InferType, AnyObject } from 'yup'
import { parse } from './parse.util'

if (IS_SERVER) require('dotenv/config')

const envSchema = object({
CLIENT_HOST: string().default('http://localhost:3000'),
CLIENT_PUBLIC_PATH: string().default('0.0.0'),
APP_VERSION: string().default('0.0.0'),
NODE_ENV: mixed<'production' | 'development' | 'test'>()
.oneOf(['production', 'development', 'test'])
.default('development')
})
interface Props<T extends AnyObject> {
schema: ObjectSchema<T>
envs: Partial<Record<keyof InferType<ObjectSchema<T>>, unknown>>
clientPrefix?: string
}
export function createEnv<S extends AnyObject>({
schema,
envs,
clientPrefix = 'CLIENT_'
}: Props<S>) {
const client = schema.pick(Object.keys(schema.shape).filter(k => k.startsWith(clientPrefix)))
const { data, error } = parse((IS_SERVER ? schema : client) as ObjectSchema<S>, envs)

export type Env = InferType<typeof envSchema>
if (error) {
console.error('❌ Invalid environment variables:', error.errors)
throw new Error('Invalid environment variables')
}

export const getENV = getOrDefault(
createEnv({
clientPrefix,
schema: envSchema,
envs: IS_SERVER ? process.env : window.env_vars
})
)
return new Proxy(data, {
get(target, prop, receiver) {
if (typeof prop !== 'string') return undefined

export const setEnvVars = (nonce: string) => {
const clientEnv = Object.entries(getENV())
.filter(([k]) => k.startsWith(clientPrefix))
.reduce<Collection<string, unknown>>((res, [k, v]) => {
res[k] = v
return res
}, {})
if (
!IS_SERVER &&
(!clientPrefix || !prop.startsWith(clientPrefix)) &&
!['toJSON', 'toString'].includes(prop)
) {
throw new Error(
`❌ Attempted to access a server-side environment variable "${prop}" on the client`
)
}

return `<script nonce='${nonce}'>window.env_vars = Object.freeze(${JSON.stringify(clientEnv)})</script>`
return Reflect.get(target, prop, receiver)
}
}) as InferType<ObjectSchema<S>>
}
38 changes: 37 additions & 1 deletion src/common/env/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,37 @@
export * from './env.util'
import { string, mixed, object, type InferType } from 'yup'
import { getOrDefault } from './get.util'
import { createEnv } from './env.util'

if (IS_SERVER) require('dotenv/config')

// you can find implementation for a `zod` library
// in the `feat/pipable-stream` git branch
const envSchema = object({
CLIENT_HOST: string().default('http://localhost:3000'),
CLIENT_PUBLIC_PATH: string().default('0.0.0'),
APP_VERSION: string().default('0.0.0'),
NODE_ENV: mixed<'production' | 'development' | 'test'>()
.oneOf(['production', 'development', 'test'])
.default('development')
})

export type Env = InferType<typeof envSchema>

export const getENV = getOrDefault(
createEnv({
clientPrefix,
schema: envSchema,
envs: IS_SERVER ? process.env : window.env_vars
})
)

export const setEnvVars = (nonce: string) => {
const clientEnv = Object.entries(getENV())
.filter(([k]) => k.startsWith(clientPrefix))
.reduce<Collection<string, unknown>>((res, [k, v]) => {
res[k] = v
return res
}, {})

return `<script nonce='${nonce}'>window.env_vars=Object.freeze(${JSON.stringify(clientEnv)})</script>`
}
4 changes: 2 additions & 2 deletions src/common/env/parse.util.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { type AnySchema, ValidationError } from 'yup'
import { type AnyObject, type ObjectSchema, ValidationError } from 'yup'

type ParseResult<T> = { data: T; error: null } | { data: null; error: ValidationError }

export function parse<T>(schema: AnySchema<T>, envs: unknown): ParseResult<T> {
export function parse<T extends AnyObject>(schema: ObjectSchema<T>, envs: unknown): ParseResult<T> {
try {
const data = schema.validateSync(envs, { abortEarly: false })
return { data, error: null } as ParseResult<T>
Expand Down

0 comments on commit 52c8182

Please sign in to comment.