Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
@@ -2,18 +2,14 @@ import type { PlopTypes } from '@turbo/gen';
|
||||
|
||||
import { createCloudflareGenerator } from './templates/cloudflare/generator';
|
||||
import { createDockerGenerator } from './templates/docker/generator';
|
||||
import { createEnvironmentVariablesGenerator } from './templates/env/generator';
|
||||
import { createKeystaticAdminGenerator } from './templates/keystatic/generator';
|
||||
import { createPackageGenerator } from './templates/package/generator';
|
||||
import { createSetupGenerator } from './templates/setup/generator';
|
||||
import { createEnvironmentVariablesValidatorGenerator } from './templates/validate-env/generator';
|
||||
|
||||
// List of generators to be registered
|
||||
const generators = [
|
||||
createPackageGenerator,
|
||||
createKeystaticAdminGenerator,
|
||||
createEnvironmentVariablesGenerator,
|
||||
createEnvironmentVariablesValidatorGenerator,
|
||||
createSetupGenerator,
|
||||
createCloudflareGenerator,
|
||||
createDockerGenerator,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { PlopTypes } from '@turbo/gen';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
import packageJson from '../../../../package.json';
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
export function createCloudflareGenerator(plop: PlopTypes.NodePlopAPI) {
|
||||
plop.setGenerator('cloudflare', {
|
||||
description: 'Cloudflare generator',
|
||||
@@ -78,9 +79,7 @@ export function createCloudflareGenerator(plop: PlopTypes.NodePlopAPI) {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
execSync(
|
||||
`pnpm run format:fix`,
|
||||
);
|
||||
execSync(`pnpm run format:fix`);
|
||||
|
||||
return 'Package scaffolded';
|
||||
},
|
||||
|
||||
@@ -65,7 +65,7 @@ ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
HEALTHCHECK --interval=90s --timeout=5s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/healthcheck || exit 1
|
||||
CMD curl -f http://localhost:3000/api/healthcheck || exit 1
|
||||
|
||||
# Start the server
|
||||
CMD ["node", "apps/web/server.js"]
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PlopTypes } from '@turbo/gen';
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import * as os from 'node:os';
|
||||
|
||||
@@ -47,7 +48,7 @@ export function createDockerGenerator(plop: PlopTypes.NodePlopAPI) {
|
||||
async () => {
|
||||
execSync('pnpm i', {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
});
|
||||
|
||||
execSync('pnpm format:fix', {
|
||||
stdio: 'inherit',
|
||||
|
||||
340
turbo/generators/templates/env/generator.ts
vendored
340
turbo/generators/templates/env/generator.ts
vendored
@@ -1,340 +0,0 @@
|
||||
import type { PlopTypes } from '@turbo/gen';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
import { generator } from '../../utils';
|
||||
|
||||
const DOCS_URL =
|
||||
'https://makerkit.dev/docs/next-supabase-turbo/environment-variables';
|
||||
|
||||
export function createEnvironmentVariablesGenerator(
|
||||
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', {
|
||||
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`;
|
||||
}
|
||||
|
||||
writeFileSync('turbo/generators/templates/env/.env.local', env);
|
||||
|
||||
return 'Environment variables generated at /turbo/generators/templates/env/.env.local.\nPlease double check and use this file in your hosting provider to set the environment variables. \nNever 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). \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_SITE_URL')}\n`,
|
||||
default: allVariables.NEXT_PUBLIC_SITE_URL,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.NEXT_PUBLIC_PRODUCT_NAME',
|
||||
message: `What is the name of your product? (Ex. MakerKit). \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_PRODUCT_NAME')}\n`,
|
||||
default: allVariables.NEXT_PUBLIC_PRODUCT_NAME,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.NEXT_PUBLIC_SITE_TITLE',
|
||||
message: `What is the title of your website? (Ex. MakerKit - The best way to make things). \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_SITE_TITLE')}\n`,
|
||||
default: allVariables.NEXT_PUBLIC_SITE_TITLE,
|
||||
},
|
||||
{
|
||||
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). \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_SITE_DESCRIPTION')}\n`,
|
||||
default: allVariables.NEXT_PUBLIC_SITE_DESCRIPTION,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'values.NEXT_PUBLIC_DEFAULT_THEME_MODE',
|
||||
message: `What is the default theme mode of your website? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_DEFAULT_THEME_MODE')}\n`,
|
||||
choices: ['light', 'dark', 'system'],
|
||||
default: allVariables.NEXT_PUBLIC_DEFAULT_THEME_MODE ?? 'light',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.NEXT_PUBLIC_DEFAULT_LOCALE',
|
||||
message: `What is the default locale of your website? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_DEFAULT_LOCALE')}\n`,
|
||||
default: allVariables.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'en',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_AUTH_PASSWORD',
|
||||
message: `Do you want to use email/password authentication? If not - we will hide the password login from the UI. \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_AUTH_PASSWORD')}\n`,
|
||||
default: getBoolean(allVariables.NEXT_PUBLIC_AUTH_PASSWORD, true),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_AUTH_MAGIC_LINK',
|
||||
message: `Do you want to use magic link authentication? If not - we will hide the magic link login from the UI. \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_AUTH_MAGIC_LINK')}\n`,
|
||||
default: getBoolean(allVariables.NEXT_PUBLIC_AUTH_MAGIC_LINK, false),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.CONTACT_EMAIL',
|
||||
message: `What is the contact email you want to receive emails to? \nFor more info: ${getUrlToDocs('CONTACT_EMAIL')}\n`,
|
||||
default: allVariables.CONTACT_EMAIL,
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_THEME_TOGGLE',
|
||||
message: `Do you want to enable the theme toggle? If not - we will hide the theme toggle from the UI. \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_THEME_TOGGLE')}\n`,
|
||||
default: getBoolean(allVariables.NEXT_PUBLIC_ENABLE_THEME_TOGGLE, true),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION',
|
||||
message: `Do you want to enable personal account deletion? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION,
|
||||
true,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING',
|
||||
message: `Do you want to enable personal account billing? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING,
|
||||
true,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS',
|
||||
message: `Do you want to enable team accounts? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS,
|
||||
true,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION',
|
||||
message: `Do you want to enable team account deletion? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION,
|
||||
true,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING',
|
||||
message: `Do you want to enable team account billing? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING,
|
||||
true,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION',
|
||||
message: `Do you want to enable team account creation? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION,
|
||||
true,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_NOTIFICATIONS',
|
||||
message: `Do you want to enable notifications? If not - we will hide the notifications bell from the UI. \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_NOTIFICATIONS')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_ENABLE_NOTIFICATIONS,
|
||||
true,
|
||||
),
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.values.NEXT_PUBLIC_ENABLE_NOTIFICATIONS,
|
||||
type: 'confirm',
|
||||
name: 'values.NEXT_PUBLIC_REALTIME_NOTIFICATIONS',
|
||||
message: `Do you want to enable realtime notifications? If yes, we will enable the realtime notifications from Supabase. If not - updated will be fetched lazily.\nFor more info: ${getUrlToDocs('NEXT_PUBLIC_REALTIME_NOTIFICATIONS')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_REALTIME_NOTIFICATIONS,
|
||||
false,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.NEXT_PUBLIC_ENABLE_VERSION_UPDATER',
|
||||
message: `Do you want to enable the version updater popup? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_ENABLE_VERSION_UPDATER')}\n`,
|
||||
default: getBoolean(
|
||||
allVariables.NEXT_PUBLIC_ENABLE_VERSION_UPDATER,
|
||||
false,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.NEXT_PUBLIC_SUPABASE_URL',
|
||||
message: `What is the Supabase URL? (Ex. https://yourapp.supabase.co).\nFor more info: ${getUrlToDocs('NEXT_PUBLIC_SUPABASE_URL')}\n`,
|
||||
default: allVariables.NEXT_PUBLIC_SUPABASE_URL,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.NEXT_PUBLIC_SUPABASE_PUBLIC_KEY',
|
||||
message: `What is the Supabase public key?\nFor more info: ${getUrlToDocs('NEXT_PUBLIC_SUPABASE_PUBLIC_KEY')}\n`,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.SUPABASE_SECRET_KEY',
|
||||
message: `What is the Supabase secret key?\nFor more info: ${getUrlToDocs('SUPABASE_SECRET_KEY')}\n`,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'values.NEXT_PUBLIC_BILLING_PROVIDER',
|
||||
message: `What is the billing provider you want to use?\nFor more info: ${getUrlToDocs('NEXT_PUBLIC_BILLING_PROVIDER')}\n`,
|
||||
choices: ['stripe', 'lemon-squeezy'],
|
||||
default: allVariables.NEXT_PUBLIC_BILLING_PROVIDER ?? '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?\nFor more info: ${getUrlToDocs('NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY')}\n`,
|
||||
default: allVariables.NEXT_PUBLIC_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? \nFor more info: ${getUrlToDocs('NEXT_PUBLIC_BILLING_PROVIDER')}\n`,
|
||||
},
|
||||
{
|
||||
when: (answers) =>
|
||||
answers.values.NEXT_PUBLIC_BILLING_PROVIDER === 'stripe',
|
||||
type: 'input',
|
||||
name: 'values.STRIPE_WEBHOOK_SECRET',
|
||||
message: `What is the Stripe webhook secret? \nFor more info: ${getUrlToDocs('STRIPE_WEBHOOK_SECRET')}\n`,
|
||||
},
|
||||
{
|
||||
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? \nFor more info: ${getUrlToDocs('LEMON_SQUEEZY_SECRET_KEY')}\n`,
|
||||
},
|
||||
{
|
||||
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? \nFor more info: ${getUrlToDocs('LEMON_SQUEEZY_STORE_ID')}\n`,
|
||||
default: allVariables.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?\nFor more info: ${getUrlToDocs('LEMON_SQUEEZY_SIGNING_SECRET')}\n`,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.SUPABASE_DB_WEBHOOK_SECRET',
|
||||
message: `What is the DB webhook secret?\nFor more info: ${getUrlToDocs('SUPABASE_DB_WEBHOOK_SECRET')}\n`,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'values.CMS_CLIENT',
|
||||
message: `What is the CMS client you want to use?\nFor more info: ${getUrlToDocs('CMS_CLIENT')}\n`,
|
||||
choices: ['keystatic', 'wordpress'],
|
||||
default: allVariables.CMS_CLIENT ?? 'keystatic',
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'values.MAILER_PROVIDER',
|
||||
message: `What is the mailer provider you want to use?\nFor more info: ${getUrlToDocs('MAILER_PROVIDER')}\n`,
|
||||
choices: ['nodemailer', 'resend'],
|
||||
default: allVariables.MAILER_PROVIDER ?? 'nodemailer',
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.values.MAILER_PROVIDER === 'resend',
|
||||
type: 'input',
|
||||
name: 'values.RESEND_API_KEY',
|
||||
message: `What is the Resend API key?\nFor more info: ${getUrlToDocs('RESEND_API_KEY')}\n`,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'values.EMAIL_SENDER',
|
||||
message: `What is the email sender? (ex. info@makerkit.dev).\nFor more info: ${getUrlToDocs('EMAIL_SENDER')}\n`,
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||
type: 'input',
|
||||
name: 'values.EMAIL_HOST',
|
||||
message: `What is the email host?\nFor more info: ${getUrlToDocs('EMAIL_HOST')}\n`,
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||
type: 'input',
|
||||
name: 'values.EMAIL_PORT',
|
||||
message: `What is the email port?\nFor more info: ${getUrlToDocs('EMAIL_PORT')}\n`,
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||
type: 'input',
|
||||
name: 'values.EMAIL_USER',
|
||||
message: `What is the email username? (check your email provider).\nFor more info: ${getUrlToDocs('EMAIL_USER')}\n`,
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||
type: 'input',
|
||||
name: 'values.EMAIL_PASSWORD',
|
||||
message: `What is the email password? (check your email provider).\nFor more info: ${getUrlToDocs('EMAIL_PASSWORD')}\n`,
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer',
|
||||
type: 'confirm',
|
||||
name: 'values.EMAIL_TLS',
|
||||
message: `Do you want to enable TLS for your emails?\nFor more info: ${getUrlToDocs('EMAIL_TLS')}\n`,
|
||||
default: getBoolean(allVariables.EMAIL_TLS, true),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'captcha',
|
||||
message: `Do you want to enable Cloudflare Captcha protection for the Auth endpoints?`,
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.captcha,
|
||||
type: 'input',
|
||||
name: 'values.NEXT_PUBLIC_CAPTCHA_SITE_KEY',
|
||||
message: `What is the Cloudflare Captcha site key? NB: this is the PUBLIC key!\nFor more info: ${getUrlToDocs('NEXT_PUBLIC_CAPTCHA_SITE_KEY')}\n`,
|
||||
},
|
||||
{
|
||||
when: (answers) => answers.captcha,
|
||||
type: 'input',
|
||||
name: 'values.CAPTCHA_SECRET_TOKEN',
|
||||
message: `What is the Cloudflare Captcha secret key? NB: this is the PRIVATE key!\nFor more info: ${getUrlToDocs('CAPTCHA_SECRET_TOKEN')}\n`,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function getBoolean(value: string | undefined, defaultValue: boolean) {
|
||||
return value === 'true' ? true : defaultValue;
|
||||
}
|
||||
|
||||
function getUrlToDocs(envVar: string) {
|
||||
return `${DOCS_URL}#${envVar.toLowerCase()}`;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PlopTypes } from '@turbo/gen';
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
export function createKeystaticAdminGenerator(plop: PlopTypes.NodePlopAPI) {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import eslintConfigBase from '@kit/eslint-config/base.js';
|
||||
|
||||
export default eslintConfigBase;
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PlopTypes } from '@turbo/gen';
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
export function createPackageGenerator(plop: PlopTypes.NodePlopAPI) {
|
||||
@@ -37,11 +38,6 @@ export function createPackageGenerator(plop: PlopTypes.NodePlopAPI) {
|
||||
path: 'packages/{{ name }}/tsconfig.json',
|
||||
templateFile: 'templates/package/tsconfig.json.hbs',
|
||||
},
|
||||
{
|
||||
type: 'add',
|
||||
path: 'packages/{{ name }}/eslint.config.mjs',
|
||||
templateFile: 'templates/package/eslint.config.mjs.hbs',
|
||||
},
|
||||
{
|
||||
type: 'add',
|
||||
path: 'packages/{{ name }}/index.ts',
|
||||
@@ -78,9 +74,7 @@ export function createPackageGenerator(plop: PlopTypes.NodePlopAPI) {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
execSync(
|
||||
`pnpm run format:fix`,
|
||||
);
|
||||
execSync(`pnpm run format:fix`);
|
||||
|
||||
return 'Package scaffolded';
|
||||
},
|
||||
|
||||
@@ -5,24 +5,11 @@
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --check \"**/*.{mjs,ts,md,json}\"",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*"
|
||||
},
|
||||
"prettier": "@kit/prettier-config"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PlopTypes } from '@turbo/gen';
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
@@ -60,7 +61,7 @@ export function createSetupGenerator(plop: PlopTypes.NodePlopAPI) {
|
||||
setupPreCommit({ setupHealthCheck: answers.setupHealthCheck });
|
||||
|
||||
return 'Project setup complete. Start developing your project!';
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
console.error('Project setup failed. Aborting package generation.');
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -76,7 +77,7 @@ function createMakerkitConfig(params: {
|
||||
const config = `{
|
||||
"projectName": "${params.projectName}",
|
||||
"username": "${params.username}"
|
||||
}`
|
||||
}`;
|
||||
|
||||
writeFileSync('.makerkitrc', config, {
|
||||
encoding: 'utf-8',
|
||||
@@ -105,7 +106,7 @@ function setupPreCommit(params: { setupHealthCheck: boolean }) {
|
||||
execSync(`chmod +x ${filePath}`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
console.error('Pre-commit hook setup failed. Aborting package generation.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
import type { PlopTypes } from '@turbo/gen';
|
||||
|
||||
// quick hack to avoid installing zod globally
|
||||
import { z } from '../../../../apps/web/node_modules/zod';
|
||||
import { generator } from '../../utils';
|
||||
|
||||
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_ACCOUNTS_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 env = generator.loadEnvironmentVariables(answers.path as string);
|
||||
|
||||
for (const key of Object.keys(env)) {
|
||||
const property = Schema[key];
|
||||
const value = env[key];
|
||||
|
||||
if (property) {
|
||||
// parse with Zod
|
||||
const { error } = property.safeParse(value);
|
||||
|
||||
if (error) {
|
||||
throw new Error(
|
||||
`Encountered a validation error for key ${key}:${value} \n\n${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',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user