Use existing environment variables as defaults for the generator command.
This commit is contained in:
100
turbo/generators/templates/env/generator.ts
vendored
100
turbo/generators/templates/env/generator.ts
vendored
@@ -1,9 +1,19 @@
|
|||||||
import type { PlopTypes } from '@turbo/gen';
|
import type { PlopTypes } from '@turbo/gen';
|
||||||
import { writeFileSync } from 'node:fs';
|
import { writeFileSync } from 'node:fs';
|
||||||
|
|
||||||
|
import { generator } from '../../utils';
|
||||||
|
|
||||||
export function createEnvironmentVariablesGenerator(
|
export function createEnvironmentVariablesGenerator(
|
||||||
plop: PlopTypes.NodePlopAPI,
|
plop: PlopTypes.NodePlopAPI,
|
||||||
) {
|
) {
|
||||||
|
const allVariables = generator.loadAllEnvironmentVariables('apps/web');
|
||||||
|
|
||||||
|
if (allVariables) {
|
||||||
|
console.log(
|
||||||
|
`Loaded ${Object.values(allVariables).length} default environment variables in your env files. We use these as defaults.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return plop.setGenerator('env', {
|
return plop.setGenerator('env', {
|
||||||
description: 'Generate the environment variables to be used in the app',
|
description: 'Generate the environment variables to be used in the app',
|
||||||
actions: [
|
actions: [
|
||||||
@@ -31,129 +41,130 @@ export function createEnvironmentVariablesGenerator(
|
|||||||
name: 'values.NEXT_PUBLIC_SITE_URL',
|
name: 'values.NEXT_PUBLIC_SITE_URL',
|
||||||
message:
|
message:
|
||||||
'What is the site URL of you website? (Ex. https://makerkit.dev)',
|
'What is the site URL of you website? (Ex. https://makerkit.dev)',
|
||||||
|
default: allVariables.NEXT_PUBLIC_SITE_URL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_PRODUCT_NAME',
|
name: 'values.NEXT_PUBLIC_PRODUCT_NAME',
|
||||||
message: 'What is the name of your product? (Ex. MakerKit)',
|
message: 'What is the name of your product? (Ex. MakerKit)',
|
||||||
|
default: allVariables.NEXT_PUBLIC_PRODUCT_NAME,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_SITE_TITLE',
|
name: 'values.NEXT_PUBLIC_SITE_TITLE',
|
||||||
message:
|
message:
|
||||||
'What is the title of your website? (Ex. MakerKit - The best way to make things)',
|
'What is the title of your website? (Ex. MakerKit - The best way to make things)',
|
||||||
|
default: allVariables.NEXT_PUBLIC_SITE_TITLE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_SITE_DESCRIPTION',
|
name: 'values.NEXT_PUBLIC_SITE_DESCRIPTION',
|
||||||
message:
|
message:
|
||||||
'What is the description of your website? (Ex. MakerKit is the best way to make things and stuff)',
|
'What is the description of your website? (Ex. MakerKit is the best way to make things and stuff)',
|
||||||
|
default: allVariables.NEXT_PUBLIC_SITE_DESCRIPTION,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'values.NEXT_PUBLIC_DEFAULT_THEME_MODE',
|
name: 'values.NEXT_PUBLIC_DEFAULT_THEME_MODE',
|
||||||
message:
|
message: 'What is the default theme mode of your website?',
|
||||||
'What is the default theme mode of your website? (leave empty for light)',
|
|
||||||
choices: ['light', 'dark', 'system'],
|
choices: ['light', 'dark', 'system'],
|
||||||
default: 'light',
|
default: allVariables.NEXT_PUBLIC_DEFAULT_THEME_MODE ?? 'light',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_DEFAULT_LOCALE',
|
name: 'values.NEXT_PUBLIC_DEFAULT_LOCALE',
|
||||||
message:
|
message: 'What is the default locale of your website?',
|
||||||
'What is the default locale of your website? (leave empty for en)',
|
default: allVariables.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'en',
|
||||||
default: 'en',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_AUTH_PASSWORD',
|
name: 'values.NEXT_PUBLIC_AUTH_PASSWORD',
|
||||||
message:
|
message: 'Do you want to use email/password authentication?',
|
||||||
'Do you want to use email/password authentication? (leave empty for true)',
|
default: allVariables.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'true',
|
||||||
default: 'true',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_AUTH_MAGIC_LINK',
|
name: 'values.NEXT_PUBLIC_AUTH_MAGIC_LINK',
|
||||||
message:
|
message:
|
||||||
'Do you want to use magic link authentication? (leave empty for false)',
|
'Do you want to use magic link authentication? (leave empty for false)',
|
||||||
default: 'false',
|
default: allVariables.NEXT_PUBLIC_AUTH_MAGIC_LINK ?? 'false',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.CONTACT_EMAIL',
|
name: 'values.CONTACT_EMAIL',
|
||||||
message: 'What is the contact email you want to receive emails to?',
|
message: 'What is the contact email you want to receive emails to?',
|
||||||
|
default: allVariables.CONTACT_EMAIL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
name: 'values.NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
||||||
message:
|
message: 'Do you want to enable the theme toggle?',
|
||||||
'Do you want to enable the theme toggle? (leave empty for true)',
|
default: allVariables.NEXT_PUBLIC_ENABLE_THEME_TOGGLE ?? 'true',
|
||||||
default: 'true',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION',
|
name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION',
|
||||||
message:
|
message: 'Do you want to enable personal account deletion?',
|
||||||
'Do you want to enable personal account deletion? (leave empty for true)',
|
default:
|
||||||
default: 'true',
|
allVariables.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION ?? 'true',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING',
|
name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING',
|
||||||
message:
|
message: 'Do you want to enable personal account billing?',
|
||||||
'Do you want to enable personal account billing? (leave empty for true)',
|
default:
|
||||||
default: 'true',
|
allVariables.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING ?? 'true',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
||||||
message: 'Do you want to enable team accounts? (leave empty for true)',
|
message: 'Do you want to enable team accounts?',
|
||||||
default: 'true',
|
default: allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS ?? 'true',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNT_DELETION',
|
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNT_DELETION',
|
||||||
message:
|
message: 'Do you want to enable team account deletion?',
|
||||||
'Do you want to enable team account deletion? (leave empty for true)',
|
default:
|
||||||
default: 'true',
|
allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNT_DELETION ?? 'true',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING',
|
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING',
|
||||||
message:
|
message: 'Do you want to enable team account billing?',
|
||||||
'Do you want to enable team account billing? (leave empty for true)',
|
default:
|
||||||
default: 'true',
|
allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING ?? 'true',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION',
|
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION',
|
||||||
message:
|
message: 'Do you want to enable team account creation?',
|
||||||
'Do you want to enable team account creation? (leave empty for true)',
|
default:
|
||||||
default: 'true',
|
allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION ?? 'true',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
name: 'values.NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
||||||
message:
|
message: 'Do you want to enable realtime notifications?',
|
||||||
'Do you want to enable realtime notifications? (leave empty for false)',
|
default: allVariables.NEXT_PUBLIC_REALTIME_NOTIFICATIONS ?? 'true',
|
||||||
default: 'false',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
name: 'values.NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
||||||
message:
|
message: 'Do you want to enable email notifications?',
|
||||||
'Do you want to enable email notifications? (leave empty for true)',
|
default: allVariables.NEXT_PUBLIC_ENABLE_NOTIFICATIONS ?? 'true',
|
||||||
default: 'true',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_SUPABASE_URL',
|
name: 'values.NEXT_PUBLIC_SUPABASE_URL',
|
||||||
message: 'What is the Supabase URL? (Ex. https://yourapp.supabase.co)',
|
message: 'What is the Supabase URL? (Ex. https://yourapp.supabase.co)',
|
||||||
|
default: allVariables.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
name: 'values.NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
||||||
message: 'What is the Supabase anon key?',
|
message: 'What is the Supabase anon key?',
|
||||||
|
default: allVariables.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
@@ -163,10 +174,9 @@ export function createEnvironmentVariablesGenerator(
|
|||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'values.NEXT_PUBLIC_BILLING_PROVIDER',
|
name: 'values.NEXT_PUBLIC_BILLING_PROVIDER',
|
||||||
message:
|
message: 'What is the billing provider you want to use?',
|
||||||
'What is the billing provider you want to use? (leave empty for stripe)',
|
|
||||||
choices: ['stripe', 'lemon-squeezy'],
|
choices: ['stripe', 'lemon-squeezy'],
|
||||||
default: 'stripe',
|
default: allVariables.NEXT_PUBLIC_BILLING_PROVIDER ?? 'stripe',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
when: (answers) =>
|
when: (answers) =>
|
||||||
@@ -174,6 +184,7 @@ export function createEnvironmentVariablesGenerator(
|
|||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY',
|
name: 'values.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY',
|
||||||
message: 'What is the Stripe publishable key?',
|
message: 'What is the Stripe publishable key?',
|
||||||
|
default: allVariables.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
when: (answers) =>
|
when: (answers) =>
|
||||||
@@ -202,6 +213,7 @@ export function createEnvironmentVariablesGenerator(
|
|||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.LEMON_SQUEEZY_STORE_ID',
|
name: 'values.LEMON_SQUEEZY_STORE_ID',
|
||||||
message: 'What is the Lemon Squeezy store ID?',
|
message: 'What is the Lemon Squeezy store ID?',
|
||||||
|
default: allVariables.LEMON_SQUEEZY_STORE_ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
when: (answers) =>
|
when: (answers) =>
|
||||||
@@ -218,17 +230,16 @@ export function createEnvironmentVariablesGenerator(
|
|||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'values.CMS_CLIENT',
|
name: 'values.CMS_CLIENT',
|
||||||
message:
|
message: 'What is the CMS client you want to use?',
|
||||||
'What is the CMS client you want to use? (leave empty for keystatic)',
|
|
||||||
choices: ['keystatic', 'wordpress'],
|
choices: ['keystatic', 'wordpress'],
|
||||||
default: 'keystatic',
|
default: allVariables.CMS_CLIENT ?? 'keystatic',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'values.MAILER_PROVIDER',
|
name: 'values.MAILER_PROVIDER',
|
||||||
message: 'What is the mailer provider you want to use?',
|
message: 'What is the mailer provider you want to use?',
|
||||||
choices: ['nodemailer', 'resend'],
|
choices: ['nodemailer', 'resend'],
|
||||||
default: 'nodemailer',
|
default: allVariables.MAILER_PROVIDER ?? 'nodemailer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
when: (answers) => answers.values.MAILER_PROVIDER === 'resend',
|
when: (answers) => answers.values.MAILER_PROVIDER === 'resend',
|
||||||
@@ -270,8 +281,7 @@ export function createEnvironmentVariablesGenerator(
|
|||||||
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'values.EMAIL_TLS',
|
name: 'values.EMAIL_TLS',
|
||||||
message:
|
message: 'Do you want to enable TLS for your emails?',
|
||||||
'Do you want to enable TLS for your emails? (leave empty for true)',
|
|
||||||
default: 'true',
|
default: 'true',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { PlopTypes } from '@turbo/gen';
|
import type { PlopTypes } from '@turbo/gen';
|
||||||
import { readFileSync } from 'node:fs';
|
|
||||||
|
|
||||||
// quick hack to avoid installing zod globally
|
// quick hack to avoid installing zod globally
|
||||||
import { z } from '../../../../apps/web/node_modules/zod';
|
import { z } from '../../../../apps/web/node_modules/zod';
|
||||||
|
import { generator } from '../../utils';
|
||||||
|
|
||||||
const BooleanStringEnum = z.enum(['true', 'false']);
|
const BooleanStringEnum = z.enum(['true', 'false']);
|
||||||
|
|
||||||
@@ -156,26 +156,11 @@ export function createEnvironmentVariablesValidatorGenerator(
|
|||||||
throw new Error('URL is required');
|
throw new Error('URL is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = readFileSync(answers.path as string, 'utf-8');
|
const env = generator.loadEnvironmentVariables(answers.path as string);
|
||||||
|
|
||||||
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`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (const key of Object.keys(env)) {
|
||||||
const property = Schema[key];
|
const property = Schema[key];
|
||||||
|
const value = env[key];
|
||||||
|
|
||||||
if (property) {
|
if (property) {
|
||||||
// parse with Zod
|
// parse with Zod
|
||||||
@@ -183,7 +168,7 @@ export function createEnvironmentVariablesValidatorGenerator(
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Encountered a validation error for key ${key}: ${JSON.stringify(error, null, 2)}`,
|
`Encountered a validation error for key ${key}:${value} \n\n${JSON.stringify(error, null, 2)}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Key ${key} is valid!`);
|
console.log(`Key ${key} is valid!`);
|
||||||
|
|||||||
40
turbo/generators/utils/index.ts
Normal file
40
turbo/generators/utils/index.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
|
||||||
|
export namespace generator {
|
||||||
|
export function loadAllEnvironmentVariables(basePath: string) {
|
||||||
|
const sharedEnv = loadEnvironmentVariables(basePath + '/.env');
|
||||||
|
const productionEnv = loadEnvironmentVariables(
|
||||||
|
basePath + '/.env.production',
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...sharedEnv,
|
||||||
|
...productionEnv,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadEnvironmentVariables(filePath: string) {
|
||||||
|
const file = readFileSync(filePath, 'utf-8');
|
||||||
|
const vars = file.split('\n').filter((line) => line.trim() !== '');
|
||||||
|
|
||||||
|
const env: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (const line of vars) {
|
||||||
|
const isComment = line.startsWith('#');
|
||||||
|
|
||||||
|
if (isComment) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [key, value] = line.split('=');
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
env[key] = value ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user