diff --git a/.eslintrc.json b/.eslintrc.json index 236ffb0..929ea29 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,12 @@ { "extends": ["next/core-web-vitals", "next/typescript", "prettier"], - "plugins": ["check-file"], + "plugins": ["check-file", "n"], "rules": { "prefer-arrow-callback": ["error"], "prefer-template": ["error"], "semi": ["error"], "quotes": ["error", "double"], + "n/no-process-env": ["error"], "check-file/filename-naming-convention": [ "error", { diff --git a/next.config.mjs b/next.config.mjs index 08e7827..2dc1ea1 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,10 @@ +import createJiti from "jiti"; +import { fileURLToPath } from "node:url"; + +const jiti = createJiti(fileURLToPath(import.meta.url)); + +jiti("./src/env/server.ts"); + /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { diff --git a/package.json b/package.json index 1dcf875..e9b3ca6 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,15 @@ }, "dependencies": { "@nextui-org/react": "^2.4.6", + "@t3-oss/env-nextjs": "^0.11.1", "@tabler/icons-react": "^3.16.0", "framer-motion": "^11.5.4", + "jiti": "^1.21.6", "next": "14.2.9", "next-themes": "^0.3.0", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "zod": "^3.23.8" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", @@ -27,6 +30,7 @@ "eslint-config-next": "14.2.9", "eslint-config-prettier": "^9.1.0", "eslint-plugin-check-file": "^2.8.0", + "eslint-plugin-n": "^17.10.2", "postcss": "^8", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "^0.6.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ed3e71..d960850 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,18 @@ importers: '@nextui-org/react': specifier: ^2.4.6 version: 2.4.6(@types/react@18.3.5)(framer-motion@11.5.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.10) + '@t3-oss/env-nextjs': + specifier: ^0.11.1 + version: 0.11.1(typescript@5.6.2)(zod@3.23.8) '@tabler/icons-react': specifier: ^3.16.0 version: 3.16.0(react@18.3.1) framer-motion: specifier: ^11.5.4 version: 11.5.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + jiti: + specifier: ^1.21.6 + version: 1.21.6 next: specifier: 14.2.9 version: 14.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -29,6 +35,9 @@ importers: react-dom: specifier: ^18 version: 18.3.1(react@18.3.1) + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@trivago/prettier-plugin-sort-imports': specifier: ^4.3.0 @@ -54,6 +63,9 @@ importers: eslint-plugin-check-file: specifier: ^2.8.0 version: 2.8.0(eslint@8.57.0) + eslint-plugin-n: + specifier: ^17.10.2 + version: 17.10.2(eslint@8.57.0) postcss: specifier: ^8 version: 8.4.45 @@ -1352,6 +1364,24 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@t3-oss/env-core@0.11.1': + resolution: {integrity: sha512-MaxOwEoG1ntCFoKJsS7nqwgcxLW1SJw238AJwfJeaz3P/8GtkxXZsPPolsz1AdYvUTbe3XvqZ/VCdfjt+3zmKw==} + peerDependencies: + typescript: '>=5.0.0' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@t3-oss/env-nextjs@0.11.1': + resolution: {integrity: sha512-rx2XL9+v6wtOqLNJbD5eD8OezKlQD1BtC0WvvtHwBgK66jnF5+wGqtgkKK4Ygie1LVmoDClths2T4tdFmRvGrQ==} + peerDependencies: + typescript: '>=5.0.0' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@tabler/icons-react@3.16.0': resolution: {integrity: sha512-u2ABvvw71+VZMmkQ9PXsBQb+xoox8YSV8+96Xbg5jocE+gqIrAJD/3dJxWN9YhEP3TBhbcFQdkY4svvhhE+FBw==} peerDependencies: @@ -1788,6 +1818,12 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-compat-utils@0.5.1: + resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + eslint-config-next@14.2.9: resolution: {integrity: sha512-aNgGqWBp2KFcuEf9zNqmv+8dBkOrdyOlCIbdtyw7fiCQioLqXNcXmalAyeNtVyE95Kwgg11bgXvuVqdxpbR79g==} peerDependencies: @@ -1846,6 +1882,12 @@ packages: peerDependencies: eslint: '>=7.28.0' + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + eslint-plugin-import@2.30.0: resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==} engines: {node: '>=4'} @@ -1862,6 +1904,12 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + eslint-plugin-n@17.10.2: + resolution: {integrity: sha512-e+s4eAf5NtJaxPhTNu3qMO0Iz40WANS93w9LQgYcvuljgvDmWi/a3rh+OrNyMHeng6aOWGJO0rCg5lH4zi8yTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.23.0' + eslint-plugin-react-hooks@4.6.2: resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} @@ -2029,6 +2077,10 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} + globals@15.9.0: + resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -3044,6 +3096,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -5211,6 +5266,19 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.7.0 + '@t3-oss/env-core@0.11.1(typescript@5.6.2)(zod@3.23.8)': + dependencies: + zod: 3.23.8 + optionalDependencies: + typescript: 5.6.2 + + '@t3-oss/env-nextjs@0.11.1(typescript@5.6.2)(zod@3.23.8)': + dependencies: + '@t3-oss/env-core': 0.11.1(typescript@5.6.2)(zod@3.23.8) + zod: 3.23.8 + optionalDependencies: + typescript: 5.6.2 + '@tabler/icons-react@3.16.0(react@18.3.1)': dependencies: '@tabler/icons': 3.16.0 @@ -5770,6 +5838,11 @@ snapshots: escape-string-regexp@4.0.0: {} + eslint-compat-utils@0.5.1(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + semver: 7.6.3 + eslint-config-next@14.2.9(eslint@8.57.0)(typescript@5.6.2): dependencies: '@next/eslint-plugin-next': 14.2.9 @@ -5838,6 +5911,13 @@ snapshots: is-glob: 4.0.3 micromatch: 4.0.8 + eslint-plugin-es-x@7.8.0(eslint@8.57.0): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.11.0 + eslint: 8.57.0 + eslint-compat-utils: 0.5.1(eslint@8.57.0) + eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 @@ -5886,6 +5966,18 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.0 + eslint-plugin-n@17.10.2(eslint@8.57.0): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + enhanced-resolve: 5.17.1 + eslint: 8.57.0 + eslint-plugin-es-x: 7.8.0(eslint@8.57.0) + get-tsconfig: 4.8.0 + globals: 15.9.0 + ignore: 5.3.2 + minimatch: 9.0.5 + semver: 7.6.3 + eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): dependencies: eslint: 8.57.0 @@ -6113,6 +6205,8 @@ snapshots: dependencies: type-fest: 0.20.2 + globals@15.9.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -7108,3 +7202,5 @@ snapshots: yaml@2.5.1: {} yocto-queue@0.1.0: {} + + zod@3.23.8: {} diff --git a/src/env/server.ts b/src/env/server.ts new file mode 100644 index 0000000..0ee8608 --- /dev/null +++ b/src/env/server.ts @@ -0,0 +1,18 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { ZodError, z } from "zod"; + +export const env = createEnv({ + server: { + NODE_ENV: z.enum(["development", "production"]), + }, + onValidationError: (error: ZodError) => { + console.error( + "❌ Invalid environment variables:", + error.flatten().fieldErrors + ); + process.exit(1); + }, + emptyStringAsUndefined: true, + // eslint-disable-next-line n/no-process-env + experimental__runtimeEnv: process.env, +});