| 1 | export type ProductionComponent = 'api' | 'auth' | 'cron' | 'mcp';
|
| 2 |
|
| 3 | const sharedVariables = [
|
| 4 | 'AUTH0_DOMAIN',
|
| 5 | 'AUTH0_CLIENT_ID',
|
| 6 | 'AUTH0_AUDIENCE',
|
| 7 | 'OPENCLASP_PUBLIC_URL',
|
| 8 | ] as const;
|
| 9 |
|
| 10 | export function productionConfigurationErrors(
|
| 11 | component: ProductionComponent,
|
| 12 | environment: NodeJS.ProcessEnv = process.env,
|
| 13 | ): string[] {
|
| 14 | if (environment.VERCEL_ENV !== 'production' && environment.NODE_ENV !== 'production') return [];
|
| 15 | const required = new Set<string>(sharedVariables);
|
| 16 | required.add('DATABASE_URL');
|
| 17 | if (component !== 'auth') required.add('OPENCLASP_RELAY_ENCRYPTION_KEY');
|
| 18 | if (component === 'cron') required.add('CRON_SECRET');
|
| 19 | const errors = [...required]
|
| 20 | .filter((name) => !environment[name]?.trim())
|
| 21 | .map((name) => `${name} is required`);
|
| 22 | const publicUrl = environment.OPENCLASP_PUBLIC_URL;
|
| 23 | if (publicUrl) {
|
| 24 | try {
|
| 25 | if (new URL(publicUrl).protocol !== 'https:')
|
| 26 | errors.push('OPENCLASP_PUBLIC_URL must use HTTPS');
|
| 27 | } catch {
|
| 28 | errors.push('OPENCLASP_PUBLIC_URL must be a valid URL');
|
| 29 | }
|
| 30 | }
|
| 31 | if (
|
| 32 | component !== 'auth' &&
|
| 33 | environment.OPENCLASP_RELAY_ENCRYPTION_KEY &&
|
| 34 | environment.OPENCLASP_RELAY_ENCRYPTION_KEY.length < 32
|
| 35 | )
|
| 36 | errors.push('OPENCLASP_RELAY_ENCRYPTION_KEY must contain at least 32 characters');
|
| 37 | if (component === 'cron' && environment.CRON_SECRET && environment.CRON_SECRET.length < 32)
|
| 38 | errors.push('CRON_SECRET must contain at least 32 characters');
|
| 39 | return errors;
|
| 40 | }
|
| 41 |
|
| 42 | export function assertProductionConfiguration(component: ProductionComponent): void {
|
| 43 | const errors = productionConfigurationErrors(component);
|
| 44 | if (errors.length) throw new Error(`Unsafe production configuration: ${errors.join('; ')}`);
|
| 45 | }
|