feat: add @tower/config package with env validation
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testMatch: ['**/*.test.ts'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "@tower/config",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"zod": "^3.23.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^29.0.0",
|
||||||
|
"jest": "^29.0.0",
|
||||||
|
"ts-jest": "^29.0.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { validateEnv } from './index';
|
||||||
|
|
||||||
|
const validEnv = {
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
DATABASE_URL: 'postgresql://tower:tower_dev@localhost:5432/tower_dev',
|
||||||
|
REDIS_URL: 'redis://localhost:6379',
|
||||||
|
JWT_SECRET: 'a_super_secret_key_that_is_at_least_32_chars_long',
|
||||||
|
} as unknown as NodeJS.ProcessEnv;
|
||||||
|
|
||||||
|
describe('validateEnv', () => {
|
||||||
|
it('returns parsed config for valid env', () => {
|
||||||
|
const result = validateEnv(validEnv);
|
||||||
|
expect(result.NODE_ENV).toBe('development');
|
||||||
|
expect(result.API_PORT).toBe(3001);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies default API_PORT of 3001 when not set', () => {
|
||||||
|
const result = validateEnv(validEnv);
|
||||||
|
expect(result.API_PORT).toBe(3001);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when DATABASE_URL is missing', () => {
|
||||||
|
const { DATABASE_URL, ...withoutDb } = validEnv as Record<string, string>;
|
||||||
|
expect(() =>
|
||||||
|
validateEnv(withoutDb as unknown as NodeJS.ProcessEnv),
|
||||||
|
).toThrow('Invalid environment variables');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when JWT_SECRET is shorter than 32 chars', () => {
|
||||||
|
expect(() =>
|
||||||
|
validateEnv({ ...validEnv, JWT_SECRET: 'tooshort' } as unknown as NodeJS.ProcessEnv),
|
||||||
|
).toThrow('Invalid environment variables');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when DATABASE_URL is not a valid URL', () => {
|
||||||
|
expect(() =>
|
||||||
|
validateEnv({ ...validEnv, DATABASE_URL: 'not-a-url' } as unknown as NodeJS.ProcessEnv),
|
||||||
|
).toThrow('Invalid environment variables');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const envSchema = z.object({
|
||||||
|
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
||||||
|
DATABASE_URL: z.string().url(),
|
||||||
|
REDIS_URL: z.string().url(),
|
||||||
|
API_PORT: z.coerce.number().default(3001),
|
||||||
|
JWT_SECRET: z.string().min(32),
|
||||||
|
MEILI_URL: z.string().url().default('http://localhost:7700'),
|
||||||
|
MEILI_MASTER_KEY: z.string().default('tower_meili_dev_key'),
|
||||||
|
LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warn', 'error']).default('info'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Env = z.infer<typeof envSchema>;
|
||||||
|
|
||||||
|
export function validateEnv(env: NodeJS.ProcessEnv = process.env): Env {
|
||||||
|
const result = envSchema.safeParse(env);
|
||||||
|
if (!result.success) {
|
||||||
|
console.error('Invalid environment variables:', result.error.format());
|
||||||
|
throw new Error('Invalid environment variables');
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user