Skip to content

Commit

Permalink
feat: add environment variables validation (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrski committed Jul 12, 2022
1 parent c436382 commit 0f0b56d
Show file tree
Hide file tree
Showing 16 changed files with 125 additions and 12 deletions.
1 change: 1 addition & 0 deletions .github/workflows/PR-CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,5 @@ jobs:
# has to be scaffolded outside the CLI project so that no lint/tsconfig are leaking
# through. this way it ensures that it is the app's configs that are being used
- run: pnpm start -y ../ci-test-app
- run: mv ../ci-test-app/.env-example ../ci-test-app/.env
- run: cd ../ci-test-app && pnpm build
35 changes: 35 additions & 0 deletions src/installers/envVars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Installer } from "./index.js";
import path from "path";
import fs from "fs-extra";
import { PKG_ROOT } from "../consts.js";

export const envVariblesInstaller: Installer = async ({
projectDir,
packages,
}) => {
const usingAuth = packages?.nextAuth.inUse;
const usingPrisma = packages?.prisma.inUse;

const envAssetDir = path.join(PKG_ROOT, "template/addons/env");

let envFile = "";

switch (true) {
case usingAuth && usingPrisma:
envFile = "env-prisma-auth.js";
break;
case usingAuth:
envFile = "env-auth.js";
break;
case usingPrisma:
envFile = "env-prisma.js";
break;
}

if (!envFile) return;

const envSchemaSrc = path.join(envAssetDir, envFile);
const envSchemaDest = path.join(projectDir, "src/server/env-schema.js");

await fs.copy(envSchemaSrc, envSchemaDest, { overwrite: true });
};
6 changes: 6 additions & 0 deletions src/installers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { PackageManager } from "../utils/getUserPkgManager.js";
import { envVariblesInstaller } from "./envVars.js";
import { nextAuthInstaller } from "./next-auth.js";
import { prismaInstaller } from "./prisma.js";
import { tailwindInstaller } from "./tailwind.js";
Expand All @@ -11,6 +12,7 @@ export const availablePackages = [
"prisma",
"tailwind",
"trpc",
"envVaribles",
] as const;

export type AvailablePackages = typeof availablePackages[number];
Expand Down Expand Up @@ -51,4 +53,8 @@ export const buildPkgInstallerMap = (
inUse: packages.includes("trpc"),
installer: trpcInstaller,
},
envVaribles: {
inUse: true,
installer: envVariblesInstaller,
},
});
1 change: 0 additions & 1 deletion src/installers/trpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export const trpcInstaller: Installer = async ({
"@trpc/client",
"@trpc/next",
"@trpc/react",
"zod",
],
devMode: false,
noInstallMode: noInstall,
Expand Down
10 changes: 10 additions & 0 deletions template/addons/env/env-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { z } = require("zod");

const envSchema = z.object({
NEXTAUTH_SECRET: z.string(),
NEXTAUTH_URL: z.string().url(),
GITHUB_ID: z.string(),
GITHUB_SECRET: z.string(),
});

module.exports.envSchema = envSchema;
12 changes: 12 additions & 0 deletions template/addons/env/env-prisma-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { z } = require("zod");

const envSchema = z.object({
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(["development", "test", "production"]),
NEXTAUTH_SECRET: z.string(),
NEXTAUTH_URL: z.string().url(),
GITHUB_ID: z.string(),
GITHUB_SECRET: z.string(),
});

module.exports.envSchema = envSchema;
8 changes: 8 additions & 0 deletions template/addons/env/env-prisma.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { z } = require("zod");

const envSchema = z.object({
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(["development", "test", "production"]),
});

module.exports.envSchema = envSchema;
5 changes: 3 additions & 2 deletions template/addons/next-auth/api-handler-prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import CredentialsProvider from "next-auth/providers/credentials";
// Prisma adapter for NextAuth, optional and can be removed
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { prisma } from "../../../server/db/client";
import { env } from "../../../server/env";

export const authOptions: NextAuthOptions = {
// Configure one or more authentication providers
adapter: PrismaAdapter(prisma),
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
clientId: env.GITHUB_ID,
clientSecret: env.GITHUB_SECRET,
}),
// ...add more providers here
CredentialsProvider({
Expand Down
5 changes: 3 additions & 2 deletions template/addons/next-auth/api-handler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import NextAuth, { type NextAuthOptions } from "next-auth";
import GithubProvider from "next-auth/providers/github";
import CredentialsProvider from "next-auth/providers/credentials";
import { env } from "../../../server/env";

export const authOptions: NextAuthOptions = {
// Configure one or more authentication providers
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
clientId: env.GITHUB_ID,
clientSecret: env.GITHUB_SECRET,
}),
// ...add more providers here
CredentialsProvider({
Expand Down
3 changes: 2 additions & 1 deletion template/addons/prisma/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// src/server/db/client.ts
import { PrismaClient } from "@prisma/client";
import { env } from "../env";

declare global {
var prisma: PrismaClient | undefined;
Expand All @@ -11,6 +12,6 @@ export const prisma =
log: ["query"],
});

if (process.env.NODE_ENV !== "production") {
if (env.NODE_ENV !== "production") {
global.prisma = prisma;
}
4 changes: 2 additions & 2 deletions template/base/.env-example
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Note that not all variables here might be in use for your selected configuration

# Prisma
DATABASE_URL=
DATABASE_URL=postgresql:https://postgres:@localhost:5832/db

# Next Auth
NEXTAUTH_SECRET=
NEXTAUTH_URL=
NEXTAUTH_URL=http:https://localhost:3000

# Next Auth Github Provider
GITHUB_ID=
Expand Down
6 changes: 4 additions & 2 deletions template/base/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { env } = require("./src/server/env");

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
};

module.exports = nextConfig
module.exports = nextConfig;
3 changes: 2 additions & 1 deletion template/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"dependencies": {
"next": "12.2.1",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"zod": "^3.17.3"
},
"devDependencies": {
"@types/node": "18.0.0",
Expand Down
7 changes: 7 additions & 0 deletions template/base/src/server/env-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { z } = require("zod");

const envSchema = z.object({
// Specify your environment variables schema here
});

module.exports.envSchema = envSchema;
29 changes: 29 additions & 0 deletions template/base/src/server/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @ts-check
/**
* This file is included in `/next.config.js` which ensures the app isn't built with invalid env vars.
* It has to be a `.js`-file to be imported there.
*/
const { envSchema } = require("./env-schema");

const env = envSchema.safeParse(process.env);

const formatErrors = (
/** @type {import('zod').ZodFormattedError<Map<string,string>,string>} */
errors,
) =>
Object.entries(errors)
.map(([name, value]) => {
if (value && "_errors" in value)
return `${name}: ${value._errors.join(", ")}\n`;
})
.filter(Boolean);

if (!env.success) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(env.error.format()),
);
process.exit(1);
}

module.exports.env = env.data;
2 changes: 1 addition & 1 deletion template/base/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
"incremental": true,
"noUncheckedIndexedAccess": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
"exclude": ["node_modules"]
}

0 comments on commit 0f0b56d

Please sign in to comment.