53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
import { z } from "zod";
|
|
|
|
const nonEmptyString = z.string().trim().min(1);
|
|
const urlString = z.url();
|
|
|
|
export type Environment = Record<string, string | undefined>;
|
|
|
|
export function requiredEnvironmentValue(
|
|
name: string,
|
|
environment: Environment = process.env,
|
|
) {
|
|
const parsed = nonEmptyString.safeParse(environment[name]);
|
|
if (!parsed.success) {
|
|
throw new Error(`Missing required environment variable: ${name}`);
|
|
}
|
|
return parsed.data;
|
|
}
|
|
|
|
export function optionalEnvironmentValue(
|
|
name: string,
|
|
environment: Environment = process.env,
|
|
) {
|
|
const parsed = nonEmptyString.safeParse(environment[name]);
|
|
return parsed.success ? parsed.data : null;
|
|
}
|
|
|
|
export function requiredEnvironmentUrl(
|
|
name: string,
|
|
environment: Environment = process.env,
|
|
) {
|
|
const value = requiredEnvironmentValue(name, environment);
|
|
const parsed = urlString.safeParse(value);
|
|
if (!parsed.success) {
|
|
throw new Error(`Environment variable ${name} must be an absolute URL`);
|
|
}
|
|
return parsed.data.replace(/\/$/, "");
|
|
}
|
|
|
|
export function readFeatureEnvironment<Names extends readonly string[]>(
|
|
feature: string,
|
|
names: Names,
|
|
environment: Environment = process.env,
|
|
): { [Name in Names[number]]: string } {
|
|
try {
|
|
return Object.fromEntries(
|
|
names.map((name) => [name, requiredEnvironmentValue(name, environment)]),
|
|
) as { [Name in Names[number]]: string };
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Invalid configuration";
|
|
throw new Error(`${feature} is not configured: ${message}`, { cause: error });
|
|
}
|
|
}
|