managarten/packages/shared-config/src/env.ts
Till-JS ef70a1af0b Commit Message feat: implement comprehensive shared packages architecture for monorepo SUMMARY: Introduce 10 shared packages to unify common code across all 4 web apps, reducing ~3,000 lines of duplicated code and establishing consistent patterns for authentication, UI components, theming, and utilities. NEW SHARED PACKAGES: - @manacore/shared-auth: Unified auth logic (token management, JWT utils, fetch interceptor, storage/device/network adapters) - @manacore/shared-auth-ui: Reusable auth UI (LoginPage, RegisterPage, OAuth buttons for Google/Apple) - @manacore/shared-tailwind: Unified Tailwind config with 4 themes (lume, nature, stone, ocean) and light/dark mode support - @manacore/shared-icons: Phosphor-based icon library (40+ icons) - @manacore/shared-ui: Atomic design system (Text, Button, Badge, Toggle, Input, Modal) - @manacore/shared-i18n: Unified i18n setup with locale detection - @manacore/shared-config: Environment validation with Zod - @manacore/shared-subscriptio n-types: Subscription type definitions - @manacore/shared-subscriptio n-ui: Subscription UI components (planned) EXTENDED PACKAGES: - @manacore/shared-types: Added auth.ts, theme.ts, ui.ts, common.ts - @manacore/shared-utils: Added format.ts, validation.ts APP MIGRATIONS: - memoro/web: Migrated login (549→46 LOC), tailwind (165→12 LOC), removed 15+ duplicate components - manacore/web: Migrated to client-side auth with shared-auth, added new components (Icon, ThemeToggle, Logo) - manadeck/web: Replaced local authService/tokenManager with shared-auth, migrated auth pages - maerchenzauber/web: Added auth setup, stores, components, routes DELETED FILES (migrated to shared packages): - OAuth buttons (Google/Apple) from memoro, manacore, manadeck - Local authService, tokenManager, deviceManager, jwt utils - Duplicate Modal, Toggle, Text components - iconPaths and ManaIcon components - Subscription-related components (CostCard, PackageCard, etc.) BENEFITS: - 92% reduction in login page code - 93% reduction in tailwind config code - Consistent theming across all apps - Single source of truth for auth logic - Easier maintenance and updates BREAKING CHANGES: - Icon imports now from @manacore/shared-icons - Modal imports from @manacore/shared-ui - OAuth config via setGoogleCl ientId()/setAppleConfig()
2025-11-24 21:09:20 +01:00

173 lines
3.7 KiB
TypeScript

/**
* Environment variable validation utilities
*/
import { z } from 'zod';
/**
* Common environment variable schemas
*/
export const envSchemas = {
/** URL schema */
url: z.string().url(),
/** Non-empty string schema */
nonEmpty: z.string().min(1),
/** Optional string schema */
optional: z.string().optional(),
/** Port number schema */
port: z.coerce.number().int().min(1).max(65535),
/** Boolean schema (accepts various formats) */
boolean: z.preprocess(
(val) => {
if (typeof val === 'boolean') return val;
if (typeof val === 'string') {
return ['true', '1', 'yes', 'on'].includes(val.toLowerCase());
}
return false;
},
z.boolean()
),
/** Number schema */
number: z.coerce.number(),
/** Positive integer schema */
positiveInt: z.coerce.number().int().positive(),
/** Email schema */
email: z.string().email(),
/** Node environment schema */
nodeEnv: z.enum(['development', 'production', 'test']).default('development'),
};
/**
* Common Supabase environment schema
*/
export const supabaseEnvSchema = z.object({
SUPABASE_URL: envSchemas.url,
SUPABASE_ANON_KEY: envSchemas.nonEmpty,
SUPABASE_SERVICE_ROLE_KEY: envSchemas.nonEmpty.optional(),
});
/**
* Common app environment schema
*/
export const appEnvSchema = z.object({
NODE_ENV: envSchemas.nodeEnv,
PORT: envSchemas.port.default(3000),
});
/**
* Create an environment config from schema
*/
export function createEnvConfig<T extends z.ZodTypeAny>(
schema: T,
env: NodeJS.ProcessEnv = process.env
): z.infer<T> {
const result = schema.safeParse(env);
if (!result.success) {
const errors = result.error.errors
.map((err) => ` ${err.path.join('.')}: ${err.message}`)
.join('\n');
throw new Error(`Environment validation failed:\n${errors}`);
}
return result.data;
}
/**
* Validate environment variables with custom schema
*/
export function validateEnv<T extends z.ZodRawShape>(
schema: z.ZodObject<T>,
env: NodeJS.ProcessEnv = process.env
): z.infer<z.ZodObject<T>> {
return createEnvConfig(schema, env);
}
/**
* Get required environment variable with type safety
*/
export function getRequiredEnv(key: string, env: NodeJS.ProcessEnv = process.env): string {
const value = env[key];
if (value === undefined || value === '') {
throw new Error(`Required environment variable "${key}" is not set`);
}
return value;
}
/**
* Get optional environment variable with default
*/
export function getEnv(
key: string,
defaultValue: string,
env: NodeJS.ProcessEnv = process.env
): string {
return env[key] ?? defaultValue;
}
/**
* Get boolean environment variable
*/
export function getBoolEnv(
key: string,
defaultValue: boolean = false,
env: NodeJS.ProcessEnv = process.env
): boolean {
const value = env[key];
if (value === undefined) {
return defaultValue;
}
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
}
/**
* Get number environment variable
*/
export function getNumEnv(
key: string,
defaultValue: number,
env: NodeJS.ProcessEnv = process.env
): number {
const value = env[key];
if (value === undefined) {
return defaultValue;
}
const parsed = Number(value);
return isNaN(parsed) ? defaultValue : parsed;
}
/**
* Check if running in development
*/
export function isDevelopment(env: NodeJS.ProcessEnv = process.env): boolean {
return env.NODE_ENV === 'development';
}
/**
* Check if running in production
*/
export function isProduction(env: NodeJS.ProcessEnv = process.env): boolean {
return env.NODE_ENV === 'production';
}
/**
* Check if running in test
*/
export function isTest(env: NodeJS.ProcessEnv = process.env): boolean {
return env.NODE_ENV === 'test';
}