* Refactor environment variables UI and update validation logic Enhanced the environment variables page layout for better responsiveness and structure by introducing new components and styles. Added `EnvListDisplay` for grouped variable display and adjusted several UI elements for clarity and consistency. Updated `NEXT_PUBLIC_SENTRY_ENVIRONMENT` validation to make it optional, aligning with updated requirements. * Add environment variable validation and enhance page headers Introduces robust validation for environment variables, ensuring correctness and contextual dependency checks. Updates page headers with titles and detailed descriptions for better usability and clarity. * Refactor variable page layout and improve code readability Rearranged className attributes in JSX for consistency and readability. Refactored map and enum validation logic for better formatting and maintainability. Applied minor corrections to types and formatting in other components. * Refactor styles and simplify component logic Updated badge variants to streamline styles and removed redundant hover states. Simplified logic in email page by extracting breadcrumb values and optimizing title rendering. Adjusted environment variables manager layout for cleaner rendering and removed unnecessary elements. * Add real-time translation updates with RxJS and UI improvements Introduced a Subject with debounce mechanism for handling translation updates, enhancing real-time editing in the translations comparison module. Improved UI components, including conditional rendering, better input handling, and layout adjustments. Implemented a server action for updating translations and streamlined type definitions in the emails page. * Enhance environment variable copying functionality and improve user feedback Updated the environment variables manager to copy structured environment variable data to the clipboard, improving usability. Adjusted toast notifications to provide clearer success and error messages during the copy process. Additionally, fixed a minor issue in the translations comparison component by ensuring proper filtering of keys based on the search input. * Add AI translation functionality and update dependencies Implemented a new action for translating missing strings using AI, enhancing the translations comparison component. Introduced a loading state during translation and improved error handling for translation updates. Updated package dependencies, including the addition of '@ai-sdk/openai' and 'ai' to facilitate AI-driven translations. Enhanced UI components for better user experience and streamlined translation management.
1267 lines
34 KiB
TypeScript
1267 lines
34 KiB
TypeScript
import { EnvMode } from '@/app/variables/lib/types';
|
|
import { z } from 'zod';
|
|
|
|
type ModelType =
|
|
| 'string'
|
|
| 'longString'
|
|
| 'number'
|
|
| 'boolean'
|
|
| 'enum'
|
|
| 'url'
|
|
| 'email';
|
|
|
|
type Values = Array<string | null>;
|
|
|
|
export type EnvVariableModel = {
|
|
name: string;
|
|
description: string;
|
|
hint?: string;
|
|
secret?: boolean;
|
|
type?: ModelType;
|
|
values?: Values;
|
|
category: string;
|
|
required?: boolean;
|
|
validate?: ({
|
|
value,
|
|
variables,
|
|
mode,
|
|
}: {
|
|
value: string;
|
|
variables: Record<string, string>;
|
|
mode: EnvMode;
|
|
}) => z.SafeParseReturnType<unknown, unknown>;
|
|
contextualValidation?: {
|
|
dependencies: Array<{
|
|
variable: string;
|
|
condition: (value: string, variables: Record<string, string>) => boolean;
|
|
message: string;
|
|
}>;
|
|
validate: ({
|
|
value,
|
|
variables,
|
|
mode,
|
|
}: {
|
|
value: string;
|
|
variables: Record<string, string>;
|
|
mode: EnvMode;
|
|
}) => z.SafeParseReturnType<unknown, unknown>;
|
|
};
|
|
};
|
|
|
|
export const envVariables: EnvVariableModel[] = [
|
|
{
|
|
name: 'NEXT_PUBLIC_SITE_URL',
|
|
description:
|
|
'The URL of your site, used for generating absolute URLs. Must include the protocol.',
|
|
category: 'Site Configuration',
|
|
required: true,
|
|
type: 'url',
|
|
hint: `Ex. https://example.com`,
|
|
validate: ({ value, mode }) => {
|
|
if (mode === 'development') {
|
|
return z
|
|
.string()
|
|
.url({
|
|
message: `The NEXT_PUBLIC_SITE_URL variable must be a valid URL`,
|
|
})
|
|
.safeParse(value);
|
|
}
|
|
|
|
return z
|
|
.string()
|
|
.url({
|
|
message: `The NEXT_PUBLIC_SITE_URL variable must be a valid URL`,
|
|
})
|
|
.startsWith(
|
|
'https',
|
|
`The NEXT_PUBLIC_SITE_URL variable must start with https`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_PRODUCT_NAME',
|
|
description:
|
|
"Your product's name, used consistently across the application interface.",
|
|
category: 'Site Configuration',
|
|
hint: `Ex. "My Product"`,
|
|
required: true,
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_PRODUCT_NAME variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_SITE_TITLE',
|
|
description:
|
|
"The site's title tag content, crucial for SEO and browser display.",
|
|
category: 'Site Configuration',
|
|
required: true,
|
|
hint: `Ex. "My Product, the best product ever"`,
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_SITE_TITLE variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_SITE_DESCRIPTION',
|
|
type: 'longString',
|
|
description:
|
|
"Your site's meta description, important for SEO optimization.",
|
|
category: 'Site Configuration',
|
|
required: true,
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_SITE_DESCRIPTION variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_DEFAULT_LOCALE',
|
|
type: 'string',
|
|
description: 'Sets the default language for your application.',
|
|
category: 'Localization',
|
|
hint: `Ex. "en"`,
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_DEFAULT_LOCALE variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_AUTH_PASSWORD',
|
|
description: 'Enables or disables password-based authentication.',
|
|
category: 'Authentication',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_AUTH_MAGIC_LINK',
|
|
description: 'Enables or disables magic link authentication.',
|
|
category: 'Authentication',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY',
|
|
description: 'Your Cloudflare Captcha site key for form protection.',
|
|
category: 'Security',
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z.string().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'CAPTCHA_SECRET_TOKEN',
|
|
description:
|
|
'Your Cloudflare Captcha secret token for backend verification.',
|
|
category: 'Security',
|
|
secret: true,
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY',
|
|
condition: (value) => {
|
|
return value !== '';
|
|
},
|
|
message:
|
|
'CAPTCHA_SECRET_TOKEN is required when NEXT_PUBLIC_CAPTCHA_SITE_KEY is set',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The CAPTCHA_SECRET_TOKEN variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The CAPTCHA_SECRET_TOKEN variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_USER_NAVIGATION_STYLE',
|
|
description:
|
|
'Controls user navigation layout. Options: sidebar, header, or custom.',
|
|
category: 'Navigation',
|
|
type: 'enum',
|
|
values: ['sidebar', 'header', 'custom'],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.enum(['sidebar', 'header', 'custom'])
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED',
|
|
description: 'Sets the default state of the home sidebar.',
|
|
category: 'Navigation',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_TEAM_NAVIGATION_STYLE',
|
|
description:
|
|
'Controls team navigation layout. Options: sidebar, header, or custom.',
|
|
category: 'Navigation',
|
|
type: 'enum',
|
|
values: ['sidebar', 'header', 'custom'],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.enum(['sidebar', 'header', 'custom'])
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED',
|
|
description: 'Sets the default state of the team sidebar.',
|
|
category: 'Navigation',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE',
|
|
description:
|
|
'Defines sidebar collapse behavior. Options: offscreen, icon, or none.',
|
|
category: 'Navigation',
|
|
type: 'enum',
|
|
values: ['offscreen', 'icon', 'none'],
|
|
validate: ({ value }) => {
|
|
return z.enum(['offscreen', 'icon', 'none']).optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_DEFAULT_THEME_MODE',
|
|
description:
|
|
'Controls the default theme appearance. Options: light, dark, or system.',
|
|
category: 'Theme',
|
|
type: 'enum',
|
|
values: ['light', 'dark', 'system'],
|
|
validate: ({ value }) => {
|
|
return z.enum(['light', 'dark', 'system']).optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
|
description: 'Controls visibility of the theme toggle feature.',
|
|
category: 'Theme',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER',
|
|
description: 'Controls visibility of the sidebar trigger feature.',
|
|
category: 'Navigation',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION',
|
|
description: 'Allows users to delete their personal accounts.',
|
|
category: 'Features',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING',
|
|
description: 'Enables billing features for personal accounts.',
|
|
category: 'Features',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
|
description: 'Master switch for team account functionality.',
|
|
category: 'Features',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION',
|
|
description: 'Controls ability to create new team accounts.',
|
|
category: 'Features',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION',
|
|
description: 'Allows team account deletion.',
|
|
category: 'Features',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING',
|
|
description: 'Enables billing features for team accounts.',
|
|
category: 'Features',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
|
description: 'Controls the notification system.',
|
|
category: 'Notifications',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
|
description: 'Enables real-time notifications using Supabase Realtime.',
|
|
category: 'Notifications',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_SUPABASE_URL',
|
|
description: 'Your Supabase project URL.',
|
|
category: 'Supabase',
|
|
hint: `Ex. https://your-project.supabase.co`,
|
|
required: true,
|
|
type: 'url',
|
|
validate: ({ value, mode }) => {
|
|
if (mode === 'development') {
|
|
return z
|
|
.string()
|
|
.url({
|
|
message: `The NEXT_PUBLIC_SUPABASE_URL variable must be a valid URL`,
|
|
})
|
|
.safeParse(value);
|
|
}
|
|
|
|
return z
|
|
.string()
|
|
.url({
|
|
message: `The NEXT_PUBLIC_SUPABASE_URL variable must be a valid URL`,
|
|
})
|
|
.startsWith(
|
|
'https',
|
|
`The NEXT_PUBLIC_SUPABASE_URL variable must start with https`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
description: 'Your Supabase anonymous API key.',
|
|
category: 'Supabase',
|
|
required: true,
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_SUPABASE_ANON_KEY variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'SUPABASE_SERVICE_ROLE_KEY',
|
|
description: 'Your Supabase service role key (keep this secret!).',
|
|
category: 'Supabase',
|
|
secret: true,
|
|
required: true,
|
|
type: 'string',
|
|
validate: ({ value, variables }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The SUPABASE_SERVICE_ROLE_KEY variable must be at least 1 character`,
|
|
)
|
|
.refine(
|
|
(value) => {
|
|
return value !== variables['NEXT_PUBLIC_SUPABASE_ANON_KEY'];
|
|
},
|
|
{
|
|
message: `The SUPABASE_SERVICE_ROLE_KEY variable must be different from NEXT_PUBLIC_SUPABASE_ANON_KEY`,
|
|
},
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'SUPABASE_DB_WEBHOOK_SECRET',
|
|
description: 'Secret key for Supabase webhook verification.',
|
|
category: 'Supabase',
|
|
secret: true,
|
|
required: true,
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The SUPABASE_DB_WEBHOOK_SECRET variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_BILLING_PROVIDER',
|
|
description:
|
|
'Your chosen billing provider. Options: stripe or lemon-squeezy.',
|
|
category: 'Billing',
|
|
required: true,
|
|
type: 'enum',
|
|
values: ['stripe', 'lemon-squeezy'],
|
|
validate: ({ value }) => {
|
|
return z.enum(['stripe', 'lemon-squeezy']).optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY',
|
|
description: 'Your Stripe publishable key.',
|
|
hint: `Ex. pk_test_123456789012345678901234`,
|
|
category: 'Billing',
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_BILLING_PROVIDER',
|
|
condition: (value) => value === 'stripe',
|
|
message:
|
|
'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must be at least 1 character`,
|
|
)
|
|
.refine(
|
|
(value) => {
|
|
return value.startsWith('pk_');
|
|
},
|
|
{
|
|
message: `The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must start with pk_`,
|
|
},
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'STRIPE_SECRET_KEY',
|
|
description: 'Your Stripe secret key.',
|
|
category: 'Billing',
|
|
hint: `Ex. sk_test_123456789012345678901234`,
|
|
secret: true,
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_BILLING_PROVIDER',
|
|
condition: (value) => value === 'stripe',
|
|
message:
|
|
'STRIPE_SECRET_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(1, `The STRIPE_SECRET_KEY variable must be at least 1 character`)
|
|
.refine(
|
|
(value) => {
|
|
return value.startsWith('sk_') || value.startsWith('rk_');
|
|
},
|
|
{
|
|
message: `The STRIPE_SECRET_KEY variable must start with sk_ or rk_`,
|
|
},
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(1, `The STRIPE_SECRET_KEY variable must be at least 1 character`)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'STRIPE_WEBHOOK_SECRET',
|
|
description: 'Your Stripe webhook secret.',
|
|
category: 'Billing',
|
|
hint: `Ex. whsec_123456789012345678901234`,
|
|
secret: true,
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_BILLING_PROVIDER',
|
|
condition: (value) => value === 'stripe',
|
|
message:
|
|
'STRIPE_WEBHOOK_SECRET is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The STRIPE_WEBHOOK_SECRET variable must be at least 1 character`,
|
|
)
|
|
.refine(
|
|
(value) => {
|
|
return value.startsWith('whsec_');
|
|
},
|
|
{
|
|
message: `The STRIPE_WEBHOOK_SECRET variable must start with whsec_`,
|
|
},
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The STRIPE_WEBHOOK_SECRET variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'LEMON_SQUEEZY_SECRET_KEY',
|
|
description: 'Your Lemon Squeezy secret key.',
|
|
category: 'Billing',
|
|
secret: true,
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_BILLING_PROVIDER',
|
|
condition: (value) => value === 'lemon-squeezy',
|
|
message:
|
|
'LEMON_SQUEEZY_SECRET_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The LEMON_SQUEEZY_SECRET_KEY variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The LEMON_SQUEEZY_SECRET_KEY variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'LEMON_SQUEEZY_STORE_ID',
|
|
description: 'Your Lemon Squeezy store ID.',
|
|
category: 'Billing',
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_BILLING_PROVIDER',
|
|
condition: (value) => value === 'lemon-squeezy',
|
|
message:
|
|
'LEMON_SQUEEZY_STORE_ID is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The LEMON_SQUEEZY_STORE_ID variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The LEMON_SQUEEZY_STORE_ID variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'LEMON_SQUEEZY_SIGNING_SECRET',
|
|
description: 'Your Lemon Squeezy signing secret.',
|
|
category: 'Billing',
|
|
secret: true,
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_BILLING_PROVIDER',
|
|
condition: (value) => value === 'lemon-squeezy',
|
|
message:
|
|
'LEMON_SQUEEZY_SIGNING_SECRET is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The LEMON_SQUEEZY_SIGNING_SECRET variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The LEMON_SQUEEZY_SIGNING_SECRET variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'MAILER_PROVIDER',
|
|
description: 'Your email service provider. Options: nodemailer or resend.',
|
|
category: 'Email',
|
|
required: true,
|
|
type: 'enum',
|
|
values: ['nodemailer', 'resend'],
|
|
validate: ({ value }) => {
|
|
return z.enum(['nodemailer', 'resend']).safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'EMAIL_SENDER',
|
|
description: 'Default sender email address.',
|
|
category: 'Email',
|
|
hint: `Ex. "Makerkit <admin@makerkit.dev>"`,
|
|
required: true,
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(1, `The EMAIL_SENDER variable must be at least 1 character`)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'CONTACT_EMAIL',
|
|
description: 'Email address for contact form submissions.',
|
|
category: 'Email',
|
|
hint: `Ex. "Makerkit <admin@makerkit.dev>"`,
|
|
required: true,
|
|
type: 'email',
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.email()
|
|
.min(1, `The CONTACT_EMAIL variable must be at least 1 character`)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'RESEND_API_KEY',
|
|
description: 'Your Resend API key.',
|
|
category: 'Email',
|
|
secret: true,
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'MAILER_PROVIDER',
|
|
condition: (value) => value === 'resend',
|
|
message:
|
|
'RESEND_API_KEY is required when MAILER_PROVIDER is set to "resend"',
|
|
},
|
|
],
|
|
validate: ({ value, variables }) => {
|
|
if (variables['MAILER_PROVIDER'] === 'resend') {
|
|
return z
|
|
.string()
|
|
.min(1, `The RESEND_API_KEY variable must be at least 1 character`)
|
|
.safeParse(value);
|
|
}
|
|
|
|
return z.string().optional().safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'EMAIL_HOST',
|
|
description: 'SMTP host for Nodemailer configuration.',
|
|
category: 'Email',
|
|
type: 'string',
|
|
hint: `Ex. "smtp.example.com"`,
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'MAILER_PROVIDER',
|
|
condition: (value) => value === 'nodemailer',
|
|
message:
|
|
'EMAIL_HOST is required when MAILER_PROVIDER is set to "nodemailer"',
|
|
},
|
|
],
|
|
validate: ({ value, variables }) => {
|
|
if (variables['MAILER_PROVIDER'] === 'nodemailer') {
|
|
return z
|
|
.string()
|
|
.min(1, 'The EMAIL_HOST variable must be at least 1 character')
|
|
.safeParse(value);
|
|
}
|
|
return z.string().optional().safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'EMAIL_PORT',
|
|
description: 'SMTP port for Nodemailer configuration.',
|
|
category: 'Email',
|
|
type: 'number',
|
|
hint: `Ex. 587 or 465`,
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'MAILER_PROVIDER',
|
|
condition: (value) => value === 'nodemailer',
|
|
message:
|
|
'EMAIL_PORT is required when MAILER_PROVIDER is set to "nodemailer"',
|
|
},
|
|
],
|
|
validate: ({ value, variables }) => {
|
|
if (variables['MAILER_PROVIDER'] === 'nodemailer') {
|
|
return z.coerce
|
|
.number()
|
|
.min(1, 'The EMAIL_PORT variable must be at least 1')
|
|
.max(65535, 'The EMAIL_PORT variable must be at most 65535')
|
|
.safeParse(value);
|
|
}
|
|
return z.coerce.number().optional().safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'EMAIL_USER',
|
|
description: 'SMTP user for Nodemailer configuration.',
|
|
category: 'Email',
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'MAILER_PROVIDER',
|
|
condition: (value) => value === 'nodemailer',
|
|
message:
|
|
'EMAIL_USER is required when MAILER_PROVIDER is set to "nodemailer"',
|
|
},
|
|
],
|
|
validate: ({ value, variables }) => {
|
|
if (variables['MAILER_PROVIDER'] === 'nodemailer') {
|
|
return z
|
|
.string()
|
|
.min(1, 'The EMAIL_USER variable must be at least 1 character')
|
|
.safeParse(value);
|
|
}
|
|
|
|
return z.string().optional().safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'EMAIL_PASSWORD',
|
|
description: 'SMTP password for Nodemailer configuration.',
|
|
category: 'Email',
|
|
secret: true,
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'MAILER_PROVIDER',
|
|
condition: (value) => value === 'nodemailer',
|
|
message:
|
|
'EMAIL_PASSWORD is required when MAILER_PROVIDER is set to "nodemailer"',
|
|
},
|
|
],
|
|
validate: ({ value, variables }) => {
|
|
if (variables['MAILER_PROVIDER'] === 'nodemailer') {
|
|
return z
|
|
.string()
|
|
.min(1, 'The EMAIL_PASSWORD variable must be at least 1 character')
|
|
.safeParse(value);
|
|
}
|
|
return z.string().optional().safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'EMAIL_TLS',
|
|
description: 'Whether to use TLS for SMTP connection. Please check this in your SMTP provider settings.',
|
|
category: 'Email',
|
|
type: 'boolean',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'MAILER_PROVIDER',
|
|
condition: (value) => value === 'nodemailer',
|
|
message:
|
|
'EMAIL_TLS is required when MAILER_PROVIDER is set to "nodemailer"',
|
|
},
|
|
],
|
|
validate: ({ value, variables }) => {
|
|
if (variables['MAILER_PROVIDER'] === 'nodemailer') {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
}
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'CMS_CLIENT',
|
|
description: 'Your chosen CMS system. Options: wordpress or keystatic.',
|
|
category: 'CMS',
|
|
type: 'enum',
|
|
values: ['wordpress', 'keystatic'],
|
|
validate: ({ value }) => {
|
|
return z.enum(['wordpress', 'keystatic']).optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND',
|
|
description: 'Your Keystatic storage kind. Options: local, cloud, github.',
|
|
category: 'CMS',
|
|
type: 'enum',
|
|
values: ['local', 'cloud', 'github'],
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'CMS_CLIENT',
|
|
condition: (value) => value === 'keystatic',
|
|
message:
|
|
'NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND is required when CMS_CLIENT is set to "keystatic"',
|
|
},
|
|
],
|
|
validate: ({ value, variables }) => {
|
|
if (variables['CMS_CLIENT'] === 'keystatic') {
|
|
return z
|
|
.enum(['local', 'cloud', 'github'])
|
|
.optional()
|
|
.safeParse(value);
|
|
}
|
|
return z.enum(['local', 'cloud', 'github']).optional().safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO',
|
|
description: 'Your Keystatic storage repo.',
|
|
category: 'CMS',
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'CMS_CLIENT',
|
|
condition: (value, variables) =>
|
|
value === 'keystatic' &&
|
|
variables['NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND'] === 'github',
|
|
message:
|
|
'NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO is required when CMS_CLIENT is set to "keystatic"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'KEYSTATIC_GITHUB_TOKEN',
|
|
description: 'Your Keystatic GitHub token.',
|
|
category: 'CMS',
|
|
secret: true,
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'CMS_CLIENT',
|
|
condition: (value, variables) =>
|
|
value === 'keystatic' &&
|
|
variables['NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND'] === 'github',
|
|
message:
|
|
'KEYSTATIC_GITHUB_TOKEN is required when CMS_CLIENT is set to "keystatic"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The KEYSTATIC_GITHUB_TOKEN variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'KEYSTATIC_PATH_PREFIX',
|
|
description: 'Your Keystatic path prefix.',
|
|
category: 'CMS',
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'CMS_CLIENT',
|
|
condition: (value) => value === 'keystatic',
|
|
message:
|
|
'KEYSTATIC_PATH_PREFIX is required when CMS_CLIENT is set to "keystatic"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z.string().safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH',
|
|
description: 'Your Keystatic content path.',
|
|
category: 'CMS',
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'CMS_CLIENT',
|
|
condition: (value) => value === 'keystatic',
|
|
message:
|
|
'NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH is required when CMS_CLIENT is set to "keystatic"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'WORDPRESS_API_URL',
|
|
description: 'WordPress API URL when using WordPress as CMS.',
|
|
category: 'CMS',
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'CMS_CLIENT',
|
|
condition: (value) => value === 'wordpress',
|
|
message:
|
|
'WORDPRESS_API_URL is required when CMS_CLIENT is set to "wordpress"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.url({
|
|
message: `The WORDPRESS_API_URL variable must be a valid URL`,
|
|
})
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_LOCALES_PATH',
|
|
description: 'The path to your locales folder.',
|
|
category: 'Localization',
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_LOCALES_PATH variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_LANGUAGE_PRIORITY',
|
|
description: 'The priority setting as to how infer the language.',
|
|
category: 'Localization',
|
|
type: 'enum',
|
|
values: ['user', 'application'],
|
|
validate: ({ value }) => {
|
|
return z.enum(['user', 'application']).optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_ENABLE_VERSION_UPDATER',
|
|
description:
|
|
'Enables the version updater to poll the latest version and notify the user.',
|
|
category: 'Features',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS',
|
|
description: 'The interval in seconds to check for updates.',
|
|
category: 'Features',
|
|
type: 'number',
|
|
validate: ({ value }) => {
|
|
return z.coerce
|
|
.number()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS variable must be at least 1 character`,
|
|
)
|
|
.max(
|
|
86400,
|
|
`The NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS variable must be at most 86400`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: `ENABLE_REACT_COMPILER`,
|
|
description: 'Enables the React compiler [experimental]',
|
|
category: 'Build',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_MONITORING_PROVIDER',
|
|
description: 'The monitoring provider to use.',
|
|
category: 'Monitoring',
|
|
type: 'enum',
|
|
values: ['baselime', 'sentry', 'none'],
|
|
validate: ({ value }) => {
|
|
return z.enum(['baselime', 'sentry', '']).optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_SENTRY_DSN',
|
|
description: 'The Sentry DSN to use.',
|
|
category: 'Monitoring',
|
|
type: `string`,
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_MONITORING_PROVIDER',
|
|
condition: (value) => value === 'sentry',
|
|
message:
|
|
'NEXT_PUBLIC_SENTRY_DSN is required when NEXT_PUBLIC_MONITORING_PROVIDER is set to "sentry"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_SENTRY_DSN variable must be at least 1 character`,
|
|
)
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_SENTRY_ENVIRONMENT',
|
|
description: 'The Sentry environment to use.',
|
|
category: 'Monitoring',
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z.string().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_BASELIME_KEY',
|
|
description: 'The Baselime key to use.',
|
|
category: 'Monitoring',
|
|
type: 'string',
|
|
contextualValidation: {
|
|
dependencies: [
|
|
{
|
|
variable: 'NEXT_PUBLIC_MONITORING_PROVIDER',
|
|
condition: (value) => value === 'baselime',
|
|
message:
|
|
'NEXT_PUBLIC_BASELIME_KEY is required when NEXT_PUBLIC_MONITORING_PROVIDER is set to "baselime"',
|
|
},
|
|
],
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_BASELIME_KEY variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'STRIPE_ENABLE_TRIAL_WITHOUT_CC',
|
|
description: 'Enables trial plans without credit card.',
|
|
category: 'Billing',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_THEME_COLOR',
|
|
description: 'The default theme color.',
|
|
category: 'Theme',
|
|
type: 'string',
|
|
required: true,
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_THEME_COLOR variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_THEME_COLOR_DARK',
|
|
description: 'The default theme color for dark mode.',
|
|
category: 'Theme',
|
|
required: true,
|
|
type: 'string',
|
|
validate: ({ value }) => {
|
|
return z
|
|
.string()
|
|
.min(
|
|
1,
|
|
`The NEXT_PUBLIC_THEME_COLOR_DARK variable must be at least 1 character`,
|
|
)
|
|
.optional()
|
|
.safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX',
|
|
description: 'Whether to display the terms checkbox during sign-up.',
|
|
category: 'Features',
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
{
|
|
name: 'ENABLE_STRICT_CSP',
|
|
description: 'Enables strict Content Security Policy (CSP) headers.',
|
|
category: 'Security',
|
|
required: false,
|
|
type: 'boolean',
|
|
validate: ({ value }) => {
|
|
return z.coerce.boolean().optional().safeParse(value);
|
|
},
|
|
},
|
|
];
|