envalid
Version:
Validation for your environment variables
86 lines (72 loc) • 2.62 kB
text/typescript
/* eslint-disable no-console */
import { EnvMissingError } from './errors'
import { ReporterOptions } from './types'
type Errors<T> = Partial<Record<keyof T, Error>>
type Logger = (data: any, ...args: any[]) => void
// The default reporter is supports a second argument, for consumers
// who want to use it with only small customizations
type ExtraOptions<T> = {
onError?: (errors: Errors<T>) => void
logger: (output: string) => void
}
const defaultLogger = console.error.bind(console)
// Apply ANSI colors to the reporter output only if we detect that we're running in Node
const isNode = !!(typeof process === 'object' && process?.versions?.node)
const colorWith = (colorCode: string) => (str: string) =>
isNode ? `\x1b[${colorCode}m${str}\x1b[0m` : str
const colors = {
blue: colorWith('34'),
white: colorWith('37'),
yellow: colorWith('33'),
}
const RULE = colors.white('================================')
// Takes the provided errors, formats them all to an output string, and passes that string output to the
// provided "logger" function.
//
// This is exposed in the public API so third-party reporters can leverage this logic if desired
export const envalidErrorFormatter = <T = any>(
errors: Errors<T>,
logger: Logger = defaultLogger,
) => {
const missingVarsOutput: string[] = []
const invalidVarsOutput: string[] = []
for (const [k, err] of Object.entries(errors)) {
if (err instanceof EnvMissingError) {
missingVarsOutput.push(` ${colors.blue(k)}: ${err.message || '(required)'}`)
} else
invalidVarsOutput.push(
` ${colors.blue(k)}: ${(err as Error)?.message || '(invalid format)'}`,
)
}
// Prepend "header" output for each section of the output:
if (invalidVarsOutput.length) {
invalidVarsOutput.unshift(` ${colors.yellow('Invalid')} environment variables:`)
}
if (missingVarsOutput.length) {
missingVarsOutput.unshift(` ${colors.yellow('Missing')} environment variables:`)
}
const output = [
RULE,
invalidVarsOutput.sort().join('\n'),
missingVarsOutput.sort().join('\n'),
RULE,
]
.filter((x) => !!x)
.join('\n')
logger(output)
}
export const defaultReporter = <T = any>(
{ errors = {} }: ReporterOptions<T>,
{ onError, logger }: ExtraOptions<T> = { logger: defaultLogger },
) => {
if (!Object.keys(errors).length) return
envalidErrorFormatter(errors, logger)
if (onError) {
onError(errors)
} else if (isNode) {
logger(colors.yellow('\n Exiting with error code 1'))
process.exit(1)
} else {
throw new TypeError('Environment validation failed')
}
}