Add environment variables generators and validators
Added a new generator for creating environment variables and a validator to check the validity of the created variables. Also updated the config file to include these new generators in the list of registered generators.
This commit is contained in:
@@ -1,10 +1,17 @@
|
|||||||
import type { PlopTypes } from '@turbo/gen';
|
import type { PlopTypes } from '@turbo/gen';
|
||||||
|
|
||||||
|
import { createEnvironmentVariablesGenerator } from './templates/env/generator';
|
||||||
import { createKeystaticAdminGenerator } from './templates/keystatic/generator';
|
import { createKeystaticAdminGenerator } from './templates/keystatic/generator';
|
||||||
import { createPackageGenerator } from './templates/package/generator';
|
import { createPackageGenerator } from './templates/package/generator';
|
||||||
|
import { createEnvironmentVariablesValidatorGenerator } from './templates/validate-env/generator';
|
||||||
|
|
||||||
// List of generators to be registered
|
// List of generators to be registered
|
||||||
const generators = [createPackageGenerator, createKeystaticAdminGenerator];
|
const generators = [
|
||||||
|
createPackageGenerator,
|
||||||
|
createKeystaticAdminGenerator,
|
||||||
|
createEnvironmentVariablesGenerator,
|
||||||
|
createEnvironmentVariablesValidatorGenerator,
|
||||||
|
];
|
||||||
|
|
||||||
export default function generator(plop: PlopTypes.NodePlopAPI): void {
|
export default function generator(plop: PlopTypes.NodePlopAPI): void {
|
||||||
generators.forEach((gen) => gen(plop));
|
generators.forEach((gen) => gen(plop));
|
||||||
|
|||||||
281
turbo/generators/templates/env/generator.ts
vendored
Normal file
281
turbo/generators/templates/env/generator.ts
vendored
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
import type { PlopTypes } from '@turbo/gen';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
export function createEnvironmentVariablesGenerator(
|
||||||
|
plop: PlopTypes.NodePlopAPI,
|
||||||
|
) {
|
||||||
|
return plop.setGenerator('env', {
|
||||||
|
description: 'Generate the environment variables to be used in the app',
|
||||||
|
actions: [
|
||||||
|
async (answers) => {
|
||||||
|
let env = '';
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(
|
||||||
|
(
|
||||||
|
answers as {
|
||||||
|
values: Record<string, string>;
|
||||||
|
}
|
||||||
|
).values,
|
||||||
|
)) {
|
||||||
|
env += `${key}=${value}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write .env.local here with values
|
||||||
|
execSync(
|
||||||
|
`echo "${env}" > turbo/generators/templates/env/out/.env.local`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return 'Environment variables generated at turbo/generators/templates/env/out/.env.local. Please double check and use this file in your hosting provider to set the environment variables. Never commit this file, it contains secrets!';
|
||||||
|
},
|
||||||
|
],
|
||||||
|
prompts: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_SITE_URL',
|
||||||
|
message:
|
||||||
|
'What is the site URL of you website? (Ex. https://makerkit.dev)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_PRODUCT_NAME',
|
||||||
|
message: 'What is the name of your product? (Ex. MakerKit)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_SITE_TITLE',
|
||||||
|
message:
|
||||||
|
'What is the title of your website? (Ex. MakerKit - The best way to make things)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_SITE_DESCRIPTION',
|
||||||
|
message:
|
||||||
|
'What is the description of your website? (Ex. MakerKit is the best way to make things and stuff)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'values.NEXT_PUBLIC_DEFAULT_THEME_MODE',
|
||||||
|
message:
|
||||||
|
'What is the default theme mode of your website? (leave empty for light)',
|
||||||
|
choices: ['light', 'dark', 'system'],
|
||||||
|
default: 'light',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_DEFAULT_LOCALE',
|
||||||
|
message:
|
||||||
|
'What is the default locale of your website? (leave empty for en)',
|
||||||
|
default: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_AUTH_PASSWORD',
|
||||||
|
message:
|
||||||
|
'Do you want to use email/password authentication? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_AUTH_MAGIC_LINK',
|
||||||
|
message:
|
||||||
|
'Do you want to use magic link authentication? (leave empty for false)',
|
||||||
|
default: 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.CONTACT_EMAIL',
|
||||||
|
message: 'What is the contact email you want to receive emails to?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
||||||
|
message:
|
||||||
|
'Do you want to enable the theme toggle? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION',
|
||||||
|
message:
|
||||||
|
'Do you want to enable personal account deletion? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING',
|
||||||
|
message:
|
||||||
|
'Do you want to enable personal account billing? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
||||||
|
message: 'Do you want to enable team accounts? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNT_DELETION',
|
||||||
|
message:
|
||||||
|
'Do you want to enable team account deletion? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING',
|
||||||
|
message:
|
||||||
|
'Do you want to enable team account billing? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION',
|
||||||
|
message:
|
||||||
|
'Do you want to enable team account creation? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
||||||
|
message:
|
||||||
|
'Do you want to enable realtime notifications? (leave empty for false)',
|
||||||
|
default: 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
||||||
|
message:
|
||||||
|
'Do you want to enable email notifications? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_SUPABASE_URL',
|
||||||
|
message: 'What is the Supabase URL? (Ex. https://yourapp.supabase.co)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
||||||
|
message: 'What is the Supabase anon key?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.SUPABASE_SERVICE_ROLE_KEY',
|
||||||
|
message: 'What is the Supabase Service Role Key?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'values.NEXT_PUBLIC_BILLING_PROVIDER',
|
||||||
|
message:
|
||||||
|
'What is the billing provider you want to use? (leave empty for stripe)',
|
||||||
|
choices: ['stripe', 'lemon-squeezy'],
|
||||||
|
default: 'stripe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) =>
|
||||||
|
answers.values.NEXT_PUBLIC_BILLING_PROVIDER === 'stripe',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY',
|
||||||
|
message: 'What is the Stripe publishable key?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) =>
|
||||||
|
answers.values.NEXT_PUBLIC_BILLING_PROVIDER === 'stripe',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.STRIPE_SECRET_KEY',
|
||||||
|
message: 'What is the Stripe secret key?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) =>
|
||||||
|
answers.values.NEXT_PUBLIC_BILLING_PROVIDER === 'stripe',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.STRIPE_WEBHOOK_SECRET',
|
||||||
|
message: 'What is the Stripe webhook secret?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) =>
|
||||||
|
answers.values.NEXT_PUBLIC_BILLING_PROVIDER === 'lemon-squeezy',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.LEMON_SQUEEZY_SECRET_KEY',
|
||||||
|
message: 'What is the Lemon Squeezy secret key?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) =>
|
||||||
|
answers.values.NEXT_PUBLIC_BILLING_PROVIDER === 'lemon-squeezy',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.LEMON_SQUEEZY_STORE_ID',
|
||||||
|
message: 'What is the Lemon Squeezy store ID?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) =>
|
||||||
|
answers.values.NEXT_PUBLIC_BILLING_PROVIDER === 'lemon-squeezy',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.LEMON_SQUEEZY_SIGNING_SECRET',
|
||||||
|
message: 'What is the Lemon Squeezy signing secret?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.SUPABASE_DB_WEBHOOK_SECRET',
|
||||||
|
message: 'What is the Supabase DB webhook secret?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'values.CMS_CLIENT',
|
||||||
|
message:
|
||||||
|
'What is the CMS client you want to use? (leave empty for keystatic)',
|
||||||
|
choices: ['keystatic', 'wordpress'],
|
||||||
|
default: 'keystatic',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'values.MAILER_PROVIDER',
|
||||||
|
message: 'What is the mailer provider you want to use?',
|
||||||
|
choices: ['nodemailer', 'resend'],
|
||||||
|
default: 'nodemailer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) => answers.values.MAILER_PROVIDER === 'resend',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.RESEND_API_KEY',
|
||||||
|
message: 'What is the Resend API key?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.EMAIL_SENDER',
|
||||||
|
message: 'What is the email sender? (ex. info@makerkit.dev)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.EMAIL_HOST',
|
||||||
|
message: 'What is the email host?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.EMAIL_PORT',
|
||||||
|
message: 'What is the email port?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.EMAIL_USER',
|
||||||
|
message: 'What is the email username? (check your email provider)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.EMAIL_PASSWORD',
|
||||||
|
message: 'What is the email password? (check your email provider)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||||
|
type: 'input',
|
||||||
|
name: 'values.EMAIL_TLS',
|
||||||
|
message: 'Do you want to enable TLS? (leave empty for true)',
|
||||||
|
default: 'true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
207
turbo/generators/templates/validate-env/generator.ts
Normal file
207
turbo/generators/templates/validate-env/generator.ts
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
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/out/.env.local',
|
||||||
|
default: 'turbo/generators/templates/env/out/.env.local',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user