-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathenv.js
More file actions
124 lines (100 loc) · 3.42 KB
/
env.js
File metadata and controls
124 lines (100 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const ENV_PATH = resolve(process.cwd(), '.env');
const parseEnvLine = (line) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) {
return null;
}
const separatorIndex = trimmed.indexOf('=');
if (separatorIndex < 0) {
return null;
}
const key = trimmed.slice(0, separatorIndex).trim();
let value = trimmed.slice(separatorIndex + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
return { key, value };
};
const loadDotEnv = () => {
if (!existsSync(ENV_PATH)) {
return;
}
const contents = readFileSync(ENV_PATH, 'utf-8');
contents.split(/\r?\n/).forEach((line) => {
const parsed = parseEnvLine(line);
if (!parsed || process.env[parsed.key] !== undefined) {
return;
}
process.env[parsed.key] = parsed.value;
import dotenv from 'dotenv';
import { z } from 'zod';
dotenv.config();
const envSchema = z.object({
VITE_SUPABASE_URL: z.string().url(),
VITE_SUPABASE_ANON_KEY: z.string().min(10),
PORT: z.string().optional(),
AUTH_TOKEN_SECRET: z.string().min(16).optional(),
AUTH_TOKEN_TTL_SECONDS: z.string().regex(/^\d+$/).optional(),
CORS_ALLOW_ORIGIN: z.string().optional(),
LOGIN_RATE_LIMIT_MAX_ATTEMPTS: z.string().regex(/^\d+$/).optional(),
LOGIN_RATE_LIMIT_WINDOW_MS: z.string().regex(/^\d+$/).optional(),
LOGIN_RATE_LIMIT_BLOCK_MS: z.string().regex(/^\d+$/).optional(),
REDIS_URL: z.string().url().optional(),
REDIS_KEY_PREFIX: z.string().optional(),
CACHE_DEFAULT_TTL_SECONDS: z.string().regex(/^\d+$/).optional(),
PRESENCE_TTL_SECONDS: z.string().regex(/^\d+$/).optional(),
EVENT_STATE_DEFAULT_TTL_SECONDS: z.string().regex(/^\d+$/).optional(),
});
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('\n❌ Invalid environment configuration:\n');
result.error.errors.forEach((err) => {
console.error(`- ${err.path.join('.')}: ${err.message}`);
});
};
const isIntegerString = (value) => typeof value === 'string' && /^\d+$/.test(value);
const validateEnv = () => {
const errors = [];
const { VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY } = process.env;
if (!VITE_SUPABASE_URL) {
errors.push('VITE_SUPABASE_URL is required');
} else {
try {
new URL(VITE_SUPABASE_URL);
} catch {
errors.push('VITE_SUPABASE_URL must be a valid URL');
}
}
if (!VITE_SUPABASE_ANON_KEY || VITE_SUPABASE_ANON_KEY.length < 10) {
errors.push('VITE_SUPABASE_ANON_KEY is required and must be at least 10 characters');
}
const numericKeys = [
'AUTH_TOKEN_TTL_SECONDS',
'LOGIN_RATE_LIMIT_MAX_ATTEMPTS',
'LOGIN_RATE_LIMIT_WINDOW_MS',
'LOGIN_RATE_LIMIT_BLOCK_MS',
'REDIS_PORT',
];
numericKeys.forEach((key) => {
const value = process.env[key];
if (value !== undefined && !isIntegerString(value)) {
errors.push(`${key} must be an integer string`);
}
});
if (process.env.AUTH_TOKEN_SECRET && process.env.AUTH_TOKEN_SECRET.length < 16) {
errors.push('AUTH_TOKEN_SECRET must be at least 16 characters when provided');
}
if (errors.length > 0) {
console.error('\n❌ Invalid environment configuration:\n');
errors.forEach((error) => console.error(`- ${error}`));
process.exit(1);
}
};
loadDotEnv();
validateEnv();
export default process.env;