From e35b5a0a4cf836db1c05b1fc3ddfef473f2c4541 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 17 Nov 2025 16:15:06 -0300 Subject: [PATCH 1/3] use varlock instead of dotenv, convert .env.example into .env.schema - replaces dotenv with varlock - convert .env.example into .env.schema - ensure env is still loaded correctly - add additional env vars found in codebase to schema clean up schema --- .env.example | 29 ---------- .env.schema | 102 ++++++++++++++++++++++++++++++++++ .gitignore | 3 + .prettierignore | 2 + .vscode/extensions.json | 3 +- CONTRIBUTING.md | 11 ++-- docs/authentication.md | 2 +- eslint.config.js | 5 +- index.js | 2 +- other/Dockerfile | 2 +- package-lock.json | 63 ++++++++++++++++++--- package.json | 6 +- playwright.config.ts | 2 +- prisma.config.ts | 9 +++ remix.init/gitignore | 3 + remix.init/index.mjs | 17 +++--- tests/setup/global-setup.ts | 2 +- tests/setup/setup-test-env.ts | 2 +- 18 files changed, 204 insertions(+), 61 deletions(-) delete mode 100644 .env.example create mode 100644 .env.schema create mode 100644 prisma.config.ts diff --git a/.env.example b/.env.example deleted file mode 100644 index c5daeb201..000000000 --- a/.env.example +++ /dev/null @@ -1,29 +0,0 @@ -LITEFS_DIR="/litefs/data" -DATABASE_PATH="./prisma/data.db" -DATABASE_URL="file:./data.db?connection_limit=1" -CACHE_DATABASE_PATH="./other/cache.db" -SESSION_SECRET="super-duper-s3cret" -HONEYPOT_SECRET="super-duper-s3cret" -RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh" -SENTRY_DSN="your-dsn" - -# this is set to a random value in the Dockerfile -INTERNAL_COMMAND_TOKEN="some-made-up-token" - -# the mocks and some code rely on these two being prefixed with "MOCK_" -# if they aren't then the real github api will be attempted -GITHUB_CLIENT_ID="MOCK_GITHUB_CLIENT_ID" -GITHUB_CLIENT_SECRET="MOCK_GITHUB_CLIENT_SECRET" -GITHUB_TOKEN="MOCK_GITHUB_TOKEN" -GITHUB_REDIRECT_URI="https://example.com/auth/github/callback" - -# set this to false to prevent search engines from indexing the website -# default to allow indexing for seo safety -ALLOW_INDEXING="true" - -# Tigris Object Storage (S3-compatible) Configuration -AWS_ACCESS_KEY_ID="mock-access-key" -AWS_SECRET_ACCESS_KEY="mock-secret-key" -AWS_REGION="auto" -AWS_ENDPOINT_URL_S3="https://fly.storage.tigris.dev" -BUCKET_NAME="mock-bucket" diff --git a/.env.schema b/.env.schema new file mode 100644 index 000000000..5f242dc3e --- /dev/null +++ b/.env.schema @@ -0,0 +1,102 @@ +# This env file uses @env-spec - see https://varlock.dev/env-spec for more info +# +# @defaultRequired=true @defaultSensitive=false +# @currentEnv=$NODE_ENV +# @generateTypes(lang=ts, path=types/env-vars.d.ts) +# ---------- + +# @type=enum(development, production, test) +NODE_ENV=development +# @type=enum(development, production, test) +MODE=$NODE_ENV + +# @type=port +PORT=3000 + +LITEFS_DIR="/litefs/data" +DATABASE_PATH="./prisma/data.db" +DATABASE_URL="file:./data.db?connection_limit=1" +CACHE_DATABASE_PATH="./other/cache.db" + +# used to secure sessions +# @sensitive +# @docs(https://stack-staging.epicweb.dev/topic/deployment) +SESSION_SECRET="super-duper-s3cret" + +# encryption seed for honeypot server +# @sensitive +# @docs(https://stack-staging.epicweb.dev/topic/deployment) +HONEYPOT_SECRET="super-duper-s3cret" + +# this is set to a random value in the Dockerfile +# @sensitive +INTERNAL_COMMAND_TOKEN="some-made-up-token" + +# set to false to prevent search engines from indexing the website (defaults to allow) +ALLOW_INDEXING=true + +# enables mocks for external services +MOCKS=forEnv(development, test) + +# will be set to curent commit sha in deployments +# @optional +COMMIT_SHA= + +# API key for Resend (email service) +# @type=string(startsWith=re_) +# @sensitive +# @optional # remove this if using resend +# @docs(https://resend.com/docs/dashboard/api-keys/introduction#what-is-an-api-key) +RESEND_API_KEY= + +# will be set to true when running in CI +CI=false + +# Sentry settings (error tracking) +# note that SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT are optional +# but enable @sentry/react-router integration and release tagging +# --- +# @type=url +# @optional # remove this if using sentry +# @example=https://examplePublicKey@o0.ingest.sentry.io/0 +# @docs(https://docs.sentry.io/concepts/key-terms/dsn-explainer/) +SENTRY_DSN= +# @optional @sensitive +SENTRY_AUTH_TOKEN= +# @required=if($SENTRY_AUTH_TOKEN) +SENTRY_ORG= +# @required=if($SENTRY_AUTH_TOKEN) +SENTRY_PROJECT= + +# GitHub settings +# +# the mocks and some code rely on these being prefixed with "MOCK_" +# if they aren't then the real github api will be attempted +# --- +GITHUB_CLIENT_ID="MOCK_GITHUB_CLIENT_ID" +# @sensitive +GITHUB_CLIENT_SECRET="MOCK_GITHUB_CLIENT_SECRET" +# @sensitive +GITHUB_TOKEN="MOCK_GITHUB_TOKEN" +# @type=url +GITHUB_REDIRECT_URI="https://example.com/auth/github/callback" + + +# Tigris Object Storage (S3-compatible) Configuration +# --- +AWS_ACCESS_KEY_ID="mock-access-key" +# @sensitive +AWS_SECRET_ACCESS_KEY="mock-secret-key" +AWS_REGION="auto" +# @type=url +AWS_ENDPOINT_URL_S3="https://fly.storage.tigris.dev" +BUCKET_NAME="mock-bucket" + +# Populated by fly.io +# --- +# current fly.io region +# @optional +FLY_REGION= +# app name as set in fly.io +# @optional +FLY_APP_NAME= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2345034e4..ef74da54f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ node_modules /build /server-build .env +.env.local +.env.*.local .cache /prisma/data.db @@ -26,3 +28,4 @@ node_modules # generated files /app/components/ui/icons .react-router/ +/types/env-vars.d.ts diff --git a/.prettierignore b/.prettierignore index f022d0280..81afeda3b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ node_modules /public/build /server-build .env +.env.* /test-results/ /playwright-report/ @@ -11,5 +12,6 @@ node_modules /tests/fixtures/email/*.json /coverage /prisma/migrations +/types/env-vars.d.ts package-lock.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3c0a690df..7eb65d6b6 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,6 +6,7 @@ "prisma.prisma", "qwtel.sqlite-viewer", "yoavbls.pretty-ts-errors", - "github.vscode-github-actions" + "github.vscode-github-actions", + "varlock.env-spec-language" ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b39faf8c..9c1c0071c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,10 +18,9 @@ instructions: 1. Fork repo 2. clone the repo -3. Copy `.env.example` into `.env` -4. Run `npm install && npm run setup -s` to install dependencies and run +3. Run `npm install && npm run setup -s` to install dependencies and run validation -5. Create a branch for your PR with `git checkout -b pr/your-branch-name` +4. Create a branch for your PR with `git checkout -b pr/your-branch-name` > Tip: Keep your `main` branch pointing at the original repository and make pull > requests from branches on your fork. To do this, run: @@ -44,10 +43,10 @@ If the setup script doesn't work, you can try to run the commands manually: git clone cd ./epic-stack -# copy the .env.example to .env +# create a file for gitignored .env overrides # everything's mocked out during development so you shouldn't need to -# change any of these values unless you want to hit real environments. -cp .env.example .env +# set anything unless you want to hit real environments. +touch .env.local # Install deps npm install diff --git a/docs/authentication.md b/docs/authentication.md index 4ffedd9d6..115288816 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -62,7 +62,7 @@ be redirected to the page with your newly created OAuth app's details. You will see your app has got `0` users and no client secrets just yet, but the Client ID has already been assigned to your app. Copy over this value to `GITHUB_CLIENT_ID` in your _.env_ file. Now hit `Generate client secret` button, -and copy over the newly generted secret to `GITHUB_CLIENT_SECRET` in the dotenv +and copy over the newly generated secret to `GITHUB_CLIENT_SECRET` in the .env file. Hit `Update application` button on your GitHub OAuth app page. Your `.env` file should resemble this (values have been redacted): diff --git a/eslint.config.js b/eslint.config.js index eede0cf74..df2409301 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,6 +9,9 @@ export default [ rules: { 'react-hooks/rules-of-hooks': 'off' }, }, { - ignores: ['.react-router/*'], + ignores: [ + '.react-router/*', + 'types/env-vars.d.ts' + ], }, ] diff --git a/index.js b/index.js index 082cd60f5..7e607504a 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import 'dotenv/config' +import 'varlock/auto-load' import * as fs from 'node:fs' import sourceMapSupport from 'source-map-support' diff --git a/other/Dockerfile b/other/Dockerfile index a80103667..4fbdc04f6 100644 --- a/other/Dockerfile +++ b/other/Dockerfile @@ -74,7 +74,7 @@ RUN echo "#!/bin/sh\nset -x\nsqlite3 \$CACHE_DATABASE_URL" > /usr/local/bin/cach WORKDIR /myapp -# Generate random value and save it to .env file which will be loaded by dotenv +# Generate random value and save it to .env file which will be loaded by varlock RUN INTERNAL_COMMAND_TOKEN=$(openssl rand -hex 32) && \ echo "INTERNAL_COMMAND_TOKEN=$INTERNAL_COMMAND_TOKEN" > .env diff --git a/package-lock.json b/package-lock.json index 36eaf419a..3a2f7a30a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,6 @@ "cookie": "^1.0.2", "cross-env": "^7.0.3", "date-fns": "^4.1.0", - "dotenv": "^16.5.0", "execa": "^9.5.2", "express": "^4.21.2", "express-rate-limit": "^7.5.0", @@ -77,6 +76,7 @@ "spin-delay": "^2.0.1", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.5", + "varlock": "^0.1.0", "vite-env-only": "^3.0.3", "zod": "^3.24.4" }, @@ -1117,6 +1117,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@env-spec/parser": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@env-spec/parser/-/parser-0.0.7.tgz", + "integrity": "sha512-PK4jVwAZA+5aa3PzVF/We48wA9r1S6GRoZH6oSf/zWvNLc9LErynLbnEmmfLBo67KVqRvaur2a7aBj3H1Mpj2w==", + "license": "MIT" + }, "node_modules/@epic-web/cachified": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/@epic-web/cachified/-/cachified-5.5.2.tgz", @@ -9406,9 +9412,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -16281,9 +16287,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -17949,6 +17955,49 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/varlock": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/varlock/-/varlock-0.1.0.tgz", + "integrity": "sha512-YDtNfFS01R3WZclQR4amKK3FYToQHCByowpGY//wLwF8Mcq6gfqKwmhIx2TgJ/LgJ9Rf0gaBKal15vr+gHMujQ==", + "license": "MIT", + "dependencies": { + "@env-spec/parser": "^0.0.7", + "debug": "^4.4.3", + "execa": "^9.6.0", + "semver": "^7.7.3", + "which": "^5.0.0" + }, + "bin": { + "varlock": "bin/cli.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/varlock/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/varlock/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 006e36fc7..2110f46f0 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "build": "run-s build:*", "build:remix": "react-router build", "build:server": "tsx ./other/build-server.ts", - "dev": "cross-env NODE_ENV=development MOCKS=true node ./server/dev-server.js", - "dev:no-mocks": "cross-env NODE_ENV=development node ./server/dev-server.js", + "dev": "cross-env node ./server/dev-server.js", + "dev:no-mocks": "cross-env MOCKS=false node ./server/dev-server.js", "format": "prettier --write .", "lint": "eslint .", "setup": "npm run build && prisma migrate deploy && prisma generate --sql && playwright install", @@ -79,7 +79,6 @@ "cookie": "^1.0.2", "cross-env": "^7.0.3", "date-fns": "^4.1.0", - "dotenv": "^16.5.0", "execa": "^9.5.2", "express": "^4.21.2", "express-rate-limit": "^7.5.0", @@ -108,6 +107,7 @@ "spin-delay": "^2.0.1", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.5", + "varlock": "^0.1.0", "vite-env-only": "^3.0.3", "zod": "^3.24.4" }, diff --git a/playwright.config.ts b/playwright.config.ts index e5d832d4b..fed00fbc3 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,5 +1,5 @@ +import 'varlock/auto-load' import { defineConfig, devices } from '@playwright/test' -import 'dotenv/config' const PORT = process.env.PORT || '3000' diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 000000000..8b8f0cf17 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,9 @@ +// This file will be introduced soon in prisma upgrade PR +// see https://github.com/epicweb-dev/epic-stack/pull/1059 + +import 'varlock/auto-load' // this loads DATABASE_URL +import { defineConfig } from 'prisma/config' + +export default defineConfig({ + earlyAccess: true, // TS type in this prisma version requires this? +}) \ No newline at end of file diff --git a/remix.init/gitignore b/remix.init/gitignore index 2345034e4..ef74da54f 100644 --- a/remix.init/gitignore +++ b/remix.init/gitignore @@ -4,6 +4,8 @@ node_modules /build /server-build .env +.env.local +.env.*.local .cache /prisma/data.db @@ -26,3 +28,4 @@ node_modules # generated files /app/components/ui/icons .react-router/ +/types/env-vars.d.ts diff --git a/remix.init/index.mjs b/remix.init/index.mjs index 3ed8d1104..45aefd3bf 100644 --- a/remix.init/index.mjs +++ b/remix.init/index.mjs @@ -33,8 +33,7 @@ async function getEpicStackVersion() { export default async function main({ rootDirectory }) { const FLY_TOML_PATH = path.join(rootDirectory, 'fly.toml') - const EXAMPLE_ENV_PATH = path.join(rootDirectory, '.env.example') - const ENV_PATH = path.join(rootDirectory, '.env') + const ENV_LOCAL_PATH = path.join(rootDirectory, '.env.local') const PKG_PATH = path.join(rootDirectory, 'package.json') const appNameRegex = escapeRegExp('epic-stack-template') @@ -47,16 +46,16 @@ export default async function main({ rootDirectory }) { .replace(/[^a-zA-Z0-9-_]/g, '-') .toLowerCase() - const [flyTomlContent, env, packageJsonString] = await Promise.all([ + const [flyTomlContent, packageJsonString] = await Promise.all([ fs.readFile(FLY_TOML_PATH, 'utf-8'), - fs.readFile(EXAMPLE_ENV_PATH, 'utf-8'), fs.readFile(PKG_PATH, 'utf-8'), ]) - const newEnv = env.replace( - /^SESSION_SECRET=.*$/m, + let newEnvLocal = [ + `# Git-ignored env var overrides for local development`, + '', `SESSION_SECRET="${getRandomString(16)}"`, - ) + ].join('\n') const newFlyTomlContent = flyTomlContent.replace( new RegExp(appNameRegex, 'g'), @@ -82,7 +81,7 @@ export default async function main({ rootDirectory }) { const fileOperationPromises = [ fs.writeFile(FLY_TOML_PATH, newFlyTomlContent), - fs.writeFile(ENV_PATH, newEnv), + fs.writeFile(ENV_LOCAL_PATH, newEnvLocal), fs.writeFile(PKG_PATH, JSON.stringify(packageJson, null, 2) + '\n'), fs.copyFile( path.join(rootDirectory, 'remix.init', 'gitignore'), @@ -363,3 +362,5 @@ async function makeFlyRequest({ query, variables }) { }).then((response) => response.json()) return json.data } + +main({ rootDirectory: process.cwd() }) diff --git a/tests/setup/global-setup.ts b/tests/setup/global-setup.ts index a81d74d30..68c1c113f 100644 --- a/tests/setup/global-setup.ts +++ b/tests/setup/global-setup.ts @@ -1,7 +1,7 @@ +import 'varlock/auto-load' import path from 'node:path' import { execaCommand } from 'execa' import fsExtra from 'fs-extra' -import 'dotenv/config' import '#app/utils/env.server.ts' import '#app/utils/cache.server.ts' diff --git a/tests/setup/setup-test-env.ts b/tests/setup/setup-test-env.ts index 8987c439c..f11883eb8 100644 --- a/tests/setup/setup-test-env.ts +++ b/tests/setup/setup-test-env.ts @@ -1,4 +1,4 @@ -import 'dotenv/config' +import 'varlock/auto-load' import './db-setup.ts' import '#app/utils/env.server.ts' // we need these to be imported first 👆 From 7c1256e810c679882be9e1b11ca66696a17f3f0f Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Thu, 13 Nov 2025 23:34:24 -0300 Subject: [PATCH 2/3] remove zod env validation --- app/entry.server.tsx | 5 ++-- app/utils/env.server.ts | 49 -------------------------------- app/utils/session.server.ts | 2 +- app/utils/toast.server.ts | 2 +- app/utils/verification.server.ts | 2 +- playwright.config.ts | 2 +- server/index.ts | 4 +-- 7 files changed, 8 insertions(+), 58 deletions(-) diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 99fdd4b88..619ecf366 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -12,17 +12,16 @@ import { type ActionFunctionArgs, type HandleDocumentRequestFunction, } from 'react-router' -import { getEnv, init } from './utils/env.server.ts' +import { getEnv } from './utils/env.server.ts' import { getInstanceInfo } from './utils/litefs.server.ts' import { NonceProvider } from './utils/nonce-provider.ts' import { makeTimings } from './utils/timing.server.ts' export const streamTimeout = 5000 -init() global.ENV = getEnv() -const MODE = process.env.NODE_ENV ?? 'development' +const MODE = process.env.NODE_ENV type DocRequestArgs = Parameters diff --git a/app/utils/env.server.ts b/app/utils/env.server.ts index e762b1e60..609c7f178 100644 --- a/app/utils/env.server.ts +++ b/app/utils/env.server.ts @@ -1,52 +1,3 @@ -import { z } from 'zod' - -const schema = z.object({ - NODE_ENV: z.enum(['production', 'development', 'test'] as const), - DATABASE_PATH: z.string(), - DATABASE_URL: z.string(), - SESSION_SECRET: z.string(), - INTERNAL_COMMAND_TOKEN: z.string(), - HONEYPOT_SECRET: z.string(), - CACHE_DATABASE_PATH: z.string(), - // If you plan on using Sentry, remove the .optional() - SENTRY_DSN: z.string().optional(), - // If you plan to use Resend, remove the .optional() - RESEND_API_KEY: z.string().optional(), - // If you plan to use GitHub auth, remove the .optional() - GITHUB_CLIENT_ID: z.string().optional(), - GITHUB_CLIENT_SECRET: z.string().optional(), - GITHUB_REDIRECT_URI: z.string().optional(), - GITHUB_TOKEN: z.string().optional(), - - ALLOW_INDEXING: z.enum(['true', 'false']).optional(), - - // Tigris Object Storage Configuration - AWS_ACCESS_KEY_ID: z.string(), - AWS_SECRET_ACCESS_KEY: z.string(), - AWS_REGION: z.string(), - AWS_ENDPOINT_URL_S3: z.string().url(), - BUCKET_NAME: z.string(), -}) - -declare global { - namespace NodeJS { - interface ProcessEnv extends z.infer {} - } -} - -export function init() { - const parsed = schema.safeParse(process.env) - - if (parsed.success === false) { - console.error( - '❌ Invalid environment variables:', - parsed.error.flatten().fieldErrors, - ) - - throw new Error('Invalid environment variables') - } -} - /** * This is used in both `entry.server.ts` and `root.tsx` to ensure that * the environment variables are set and globally available before the app is diff --git a/app/utils/session.server.ts b/app/utils/session.server.ts index 5d9fd3284..1138120db 100644 --- a/app/utils/session.server.ts +++ b/app/utils/session.server.ts @@ -6,7 +6,7 @@ export const authSessionStorage = createCookieSessionStorage({ sameSite: 'lax', // CSRF protection is advised if changing to 'none' path: '/', httpOnly: true, - secrets: process.env.SESSION_SECRET.split(','), + secrets: [process.env.SESSION_SECRET], secure: process.env.NODE_ENV === 'production', }, }) diff --git a/app/utils/toast.server.ts b/app/utils/toast.server.ts index b46fefa6e..eb304aebd 100644 --- a/app/utils/toast.server.ts +++ b/app/utils/toast.server.ts @@ -21,7 +21,7 @@ export const toastSessionStorage = createCookieSessionStorage({ sameSite: 'lax', path: '/', httpOnly: true, - secrets: process.env.SESSION_SECRET.split(','), + secrets: [process.env.SESSION_SECRET], secure: process.env.NODE_ENV === 'production', }, }) diff --git a/app/utils/verification.server.ts b/app/utils/verification.server.ts index 1099f7c75..0226bd96d 100644 --- a/app/utils/verification.server.ts +++ b/app/utils/verification.server.ts @@ -7,7 +7,7 @@ export const verifySessionStorage = createCookieSessionStorage({ path: '/', httpOnly: true, maxAge: 60 * 10, // 10 minutes - secrets: process.env.SESSION_SECRET.split(','), + secrets: [process.env.SESSION_SECRET], secure: process.env.NODE_ENV === 'production', }, }) diff --git a/playwright.config.ts b/playwright.config.ts index fed00fbc3..ba8bdc4a6 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,7 +1,7 @@ import 'varlock/auto-load' import { defineConfig, devices } from '@playwright/test' -const PORT = process.env.PORT || '3000' +const PORT = process.env.PORT export default defineConfig({ testDir: './tests/e2e', diff --git a/server/index.ts b/server/index.ts index b3103284c..d9308ea03 100644 --- a/server/index.ts +++ b/server/index.ts @@ -11,7 +11,7 @@ import getPort, { portNumbers } from 'get-port' import morgan from 'morgan' import { type ServerBuild } from 'react-router' -const MODE = process.env.NODE_ENV ?? 'development' +const MODE = process.env.NODE_ENV const IS_PROD = MODE === 'production' const IS_DEV = MODE === 'development' const ALLOW_INDEXING = process.env.ALLOW_INDEXING !== 'false' @@ -213,7 +213,7 @@ app.all( }), ) -const desiredPort = Number(process.env.PORT || 3000) +const desiredPort = Number(process.env.PORT) const portToUse = await getPort({ port: portNumbers(desiredPort, desiredPort + 100), }) From 964c9b129f9fd89d07b25f145d6a48ce90c75641 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Fri, 14 Nov 2025 15:48:17 -0300 Subject: [PATCH 3/3] use varlock ENV - remove extra getEnv() + global ENV logic - use ENV from varlock/env instead of process.env - remove extra coercion/validation - add MODE to schema (we should merge this with NODE_ENV) --- app/entry.client.tsx | 1 + app/entry.server.tsx | 19 ++++++-------- app/root.tsx | 18 +++---------- .../_auth/auth.$provider/callback.test.ts | 3 ++- app/routes/_auth/webauthn/utils.server.ts | 5 ++-- app/routes/admin/cache/sqlite.server.ts | 5 ++-- app/routes/resources/images.tsx | 5 ++-- app/utils/cache.server.ts | 3 ++- app/utils/email.server.ts | 5 ++-- app/utils/env.server.ts | 25 ------------------- app/utils/honeypot.server.ts | 5 ++-- app/utils/monitoring.client.tsx | 1 + app/utils/providers/github.server.ts | 21 ++++++++-------- app/utils/session.server.ts | 5 ++-- app/utils/storage.server.ts | 19 ++++++-------- app/utils/toast.server.ts | 5 ++-- app/utils/verification.server.ts | 5 ++-- docs/monitoring.md | 4 +-- index.js | 5 ++-- package-lock.json | 17 +++++++++++++ package.json | 1 + playwright.config.ts | 17 ++++++------- react-router.config.ts | 5 ++-- server/dev-server.js | 1 + server/index.ts | 15 ++++++----- server/utils/monitoring.ts | 7 +++--- tests/mocks/github.ts | 5 ++-- tests/mocks/index.ts | 3 ++- tests/mocks/tigris.ts | 7 +++--- tests/setup/global-setup.ts | 1 - tests/setup/setup-test-env.ts | 1 - vite.config.ts | 19 ++++++++------ 32 files changed, 123 insertions(+), 135 deletions(-) delete mode 100644 app/utils/env.server.ts diff --git a/app/entry.client.tsx b/app/entry.client.tsx index 9b7749f3a..69984510f 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -1,6 +1,7 @@ import { startTransition } from 'react' import { hydrateRoot } from 'react-dom/client' import { HydratedRouter } from 'react-router/dom' +import { ENV } from 'varlock/env' if (ENV.MODE === 'production' && ENV.SENTRY_DSN) { void import('./utils/monitoring.client.tsx').then(({ init }) => init()) diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 619ecf366..f1c7cbda9 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -12,16 +12,13 @@ import { type ActionFunctionArgs, type HandleDocumentRequestFunction, } from 'react-router' -import { getEnv } from './utils/env.server.ts' +import { ENV } from 'varlock/env' import { getInstanceInfo } from './utils/litefs.server.ts' import { NonceProvider } from './utils/nonce-provider.ts' import { makeTimings } from './utils/timing.server.ts' export const streamTimeout = 5000 -global.ENV = getEnv() - -const MODE = process.env.NODE_ENV type DocRequestArgs = Parameters @@ -29,12 +26,12 @@ export default async function handleRequest(...args: DocRequestArgs) { const [request, responseStatusCode, responseHeaders, reactRouterContext] = args const { currentInstance, primaryInstance } = await getInstanceInfo() - responseHeaders.set('fly-region', process.env.FLY_REGION ?? 'unknown') - responseHeaders.set('fly-app', process.env.FLY_APP_NAME ?? 'unknown') + responseHeaders.set('fly-region', ENV.FLY_REGION ?? 'unknown') + responseHeaders.set('fly-app', ENV.FLY_APP_NAME ?? 'unknown') responseHeaders.set('fly-primary-instance', primaryInstance) responseHeaders.set('fly-instance', currentInstance) - if (process.env.NODE_ENV === 'production' && process.env.SENTRY_DSN) { + if (ENV.NODE_ENV === 'production' && ENV.SENTRY_DSN) { responseHeaders.append('Document-Policy', 'js-profiling') } @@ -71,8 +68,8 @@ export default async function handleRequest(...args: DocRequestArgs) { directives: { fetch: { 'connect-src': [ - MODE === 'development' ? 'ws:' : undefined, - process.env.SENTRY_DSN ? '*.sentry.io' : undefined, + ENV.MODE === 'development' ? 'ws:' : undefined, + ENV.SENTRY_DSN ? '*.sentry.io' : undefined, "'self'", ], 'font-src': ["'self'"], @@ -113,8 +110,8 @@ export default async function handleRequest(...args: DocRequestArgs) { export async function handleDataRequest(response: Response) { const { currentInstance, primaryInstance } = await getInstanceInfo() - response.headers.set('fly-region', process.env.FLY_REGION ?? 'unknown') - response.headers.set('fly-app', process.env.FLY_APP_NAME ?? 'unknown') + response.headers.set('fly-region', ENV.FLY_REGION ?? 'unknown') + response.headers.set('fly-app', ENV.FLY_APP_NAME ?? 'unknown') response.headers.set('fly-primary-instance', primaryInstance) response.headers.set('fly-instance', currentInstance) diff --git a/app/root.tsx b/app/root.tsx index 3b19435c1..93038c125 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -11,6 +11,7 @@ import { useMatches, } from 'react-router' import { HoneypotProvider } from 'remix-utils/honeypot/react' +import { ENV } from 'varlock/env' import { type Route } from './+types/root.ts' import appleTouchIconAssetUrl from './assets/favicons/apple-touch-icon.png' import faviconAssetUrl from './assets/favicons/favicon.svg' @@ -31,7 +32,6 @@ import tailwindStyleSheetUrl from './styles/tailwind.css?url' import { getUserId, logout } from './utils/auth.server.ts' import { ClientHintCheck, getHints } from './utils/client-hints.tsx' import { prisma } from './utils/db.server.ts' -import { getEnv } from './utils/env.server.ts' import { pipeHeaders } from './utils/headers.server.ts' import { honeypot } from './utils/honeypot.server.ts' import { combineHeaders, getDomainUrl, getImgSrc } from './utils/misc.tsx' @@ -119,7 +119,6 @@ export async function loader({ request }: Route.LoaderArgs) { theme: getTheme(request), }, }, - ENV: getEnv(), toast, honeyProps, }, @@ -138,14 +137,11 @@ function Document({ children, nonce, theme = 'light', - env = {}, }: { children: React.ReactNode nonce: string theme?: Theme - env?: Record }) { - const allowIndexing = ENV.ALLOW_INDEXING !== 'false' return ( @@ -153,19 +149,13 @@ function Document({ - {allowIndexing ? null : ( + {ENV.ALLOW_INDEXING ? null : ( )} {children} -