This commit replaces the previous use of execSync to generate .env.local files with the use of writeFileSync. It also updates the corresponding path and default value in the validation process. This change provides a more efficient and safer way to create these files and correctly update the related prompts.
208 lines
7.1 KiB
TypeScript
208 lines
7.1 KiB
TypeScript
import type { PlopTypes } from '@turbo/gen';
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
// quick hack to avoid installing zod globally
|
|
import { z } from '../../../../apps/web/node_modules/zod';
|
|
|
|
const BooleanStringEnum = z.enum(['true', 'false']);
|
|
|
|
const Schema: Record<string, z.ZodType> = {
|
|
NEXT_PUBLIC_SITE_URL: z
|
|
.string({
|
|
description: `This is the URL of your website. It should start with https:// like https://makerkit.dev.`,
|
|
})
|
|
.url({
|
|
message:
|
|
'NEXT_PUBLIC_SITE_URL must be a valid URL. Please use HTTPS for production sites, otherwise it will fail.',
|
|
})
|
|
.refine(
|
|
(url) => {
|
|
return url.startsWith('https://');
|
|
},
|
|
{
|
|
message: 'NEXT_PUBLIC_SITE_URL must start with https://',
|
|
path: ['NEXT_PUBLIC_SITE_URL'],
|
|
},
|
|
),
|
|
NEXT_PUBLIC_PRODUCT_NAME: z
|
|
.string({
|
|
message: 'Product name must be a string',
|
|
description: `This is the name of your product. It should be a short name like MakerKit.`,
|
|
})
|
|
.min(1),
|
|
NEXT_PUBLIC_SITE_DESCRIPTION: z.string({
|
|
message: 'Site description must be a string',
|
|
description: `This is the description of your website. It should be a short sentence or two.`,
|
|
}),
|
|
NEXT_PUBLIC_DEFAULT_THEME_MODE: z.enum(['light', 'dark', 'system'], {
|
|
message: 'Default theme mode must be light, dark or system',
|
|
description: `This is the default theme mode for your website. It should be light, dark or system.`,
|
|
}),
|
|
NEXT_PUBLIC_DEFAULT_LOCALE: z.string({
|
|
message: 'Default locale must be a string',
|
|
description: `This is the default locale for your website. It should be a two-letter code like en or fr.`,
|
|
}),
|
|
CONTACT_EMAIL: z
|
|
.string({
|
|
message: 'Contact email must be a valid email',
|
|
description: `This is the email address that will receive contact form submissions.`,
|
|
})
|
|
.email(),
|
|
NEXT_PUBLIC_ENABLE_THEME_TOGGLE: BooleanStringEnum,
|
|
NEXT_PUBLIC_AUTH_PASSWORD: BooleanStringEnum,
|
|
NEXT_PUBLIC_AUTH_MAGIC_LINK: BooleanStringEnum,
|
|
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION: BooleanStringEnum,
|
|
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING: BooleanStringEnum,
|
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS: BooleanStringEnum,
|
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNT_DELETION: BooleanStringEnum,
|
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING: BooleanStringEnum,
|
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION: BooleanStringEnum,
|
|
NEXT_PUBLIC_REALTIME_NOTIFICATIONS: BooleanStringEnum,
|
|
NEXT_PUBLIC_ENABLE_NOTIFICATIONS: BooleanStringEnum,
|
|
NEXT_PUBLIC_SUPABASE_URL: z
|
|
.string({
|
|
description: `This is the URL to your hosted Supabase instance.`,
|
|
})
|
|
.url({
|
|
message: 'Supabase URL must be a valid URL',
|
|
}),
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string({
|
|
message: 'Supabase anon key must be a string',
|
|
description: `This is the key provided by Supabase. It is a public key used client-side.`,
|
|
}),
|
|
SUPABASE_SERVICE_ROLE_KEY: z.string({
|
|
message: 'Supabase service role key must be a string',
|
|
description: `This is the key provided by Supabase. It is a private key used server-side.`,
|
|
}),
|
|
NEXT_PUBLIC_BILLING_PROVIDER: z.enum(['stripe', 'lemon-squeezy'], {
|
|
message: 'Billing provider must be stripe or lemon-squeezy',
|
|
description: `This is the billing provider you want to use. It should be stripe or lemon-squeezy.`,
|
|
}),
|
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z
|
|
.string({
|
|
message: 'Stripe publishable key must be a string',
|
|
description: `This is the publishable key from your Stripe dashboard. It should start with pk_`,
|
|
})
|
|
.refine(
|
|
(value) => {
|
|
return value.startsWith('pk_');
|
|
},
|
|
{
|
|
message: 'Stripe publishable key must start with pk_',
|
|
path: ['NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY'],
|
|
},
|
|
),
|
|
STRIPE_SECRET_KEY: z
|
|
.string({
|
|
message: 'Stripe secret key must be a string',
|
|
description: `This is the secret key from your Stripe dashboard. It should start with sk_`,
|
|
})
|
|
.refine(
|
|
(value) => {
|
|
return value.startsWith('sk_');
|
|
},
|
|
{
|
|
message: 'Stripe secret key must start with sk_',
|
|
path: ['STRIPE_SECRET_KEY'],
|
|
},
|
|
),
|
|
STRIPE_WEBHOOK_SECRET: z
|
|
.string({
|
|
message: 'Stripe webhook secret must be a string',
|
|
description: `This is the signing secret you copy after creating a webhook in your Stripe dashboard.`,
|
|
})
|
|
.min(1)
|
|
.refine(
|
|
(value) => {
|
|
return value.startsWith('whsec_');
|
|
},
|
|
{
|
|
message: 'Stripe webhook secret must start with whsec_',
|
|
path: ['STRIPE_WEBHOOK_SECRET'],
|
|
},
|
|
),
|
|
LEMON_SQUEEZY_SECRET_KEY: z
|
|
.string({
|
|
message: 'Lemon Squeezy API key must be a string',
|
|
description: `This is the API key from your Lemon Squeezy account`,
|
|
})
|
|
.min(1),
|
|
LEMON_SQUEEZY_STORE_ID: z
|
|
.string({
|
|
message: 'Lemon Squeezy store ID must be a string',
|
|
description: `This is the store ID of your Lemon Squeezy account`,
|
|
})
|
|
.min(1),
|
|
LEMON_SQUEEZY_SIGNING_SECRET: z
|
|
.string({
|
|
message: 'Lemon Squeezy signing secret must be a string',
|
|
description: `This is a shared secret that you must set in your Lemon Squeezy account when you create an API Key`,
|
|
})
|
|
.min(1),
|
|
MAILER_PROVIDER: z.enum(['nodemailer', 'resend'], {
|
|
message: 'Mailer provider must be nodemailer or resend',
|
|
description: `This is the mailer provider you want to use for sending emails. nodemailer is a generic SMTP mailer, resend is a service.`,
|
|
}),
|
|
};
|
|
|
|
export function createEnvironmentVariablesValidatorGenerator(
|
|
plop: PlopTypes.NodePlopAPI,
|
|
) {
|
|
return plop.setGenerator('validate-env', {
|
|
description: 'Validate the environment variables to be used in the app',
|
|
actions: [
|
|
async (answers) => {
|
|
if (!('path' in answers) || !answers.path) {
|
|
throw new Error('URL is required');
|
|
}
|
|
|
|
const file = readFileSync(answers.path as string, 'utf-8');
|
|
|
|
if (!file) {
|
|
throw new Error('File is empty');
|
|
}
|
|
|
|
const vars = file.split('\n').filter((line) => line.trim() !== '');
|
|
|
|
for (const line of vars) {
|
|
const [key, value] = line.split('=');
|
|
|
|
if (!key) {
|
|
throw new Error(`The line ${line} has no key`);
|
|
}
|
|
|
|
if (!value) {
|
|
console.warn(`The value ${key} has no value`);
|
|
}
|
|
|
|
const property = Schema[key];
|
|
|
|
if (property) {
|
|
// parse with Zod
|
|
const { error } = property.safeParse(value);
|
|
|
|
if (error) {
|
|
throw new Error(
|
|
`Encountered a validation error for key ${key}: ${JSON.stringify(error, null, 2)}`,
|
|
);
|
|
} else {
|
|
console.log(`Key ${key} is valid!`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 'Environment variables are valid!';
|
|
},
|
|
],
|
|
prompts: [
|
|
{
|
|
type: 'input',
|
|
name: 'path',
|
|
message:
|
|
'Where is the path to the environment variables file? Leave empty to use the generated turbo/generators/templates/env/.env.local',
|
|
default: 'turbo/generators/templates/env/.env.local',
|
|
},
|
|
],
|
|
});
|
|
}
|