diff --git a/.env/.env.development b/.env/.env.development
new file mode 100644
index 00000000..4ad6150f
--- /dev/null
+++ b/.env/.env.development
@@ -0,0 +1 @@
+PUBLIC_WEBSITE_URL="http://localhost:3000"
diff --git a/.env/.env.example b/.env/.env.example
new file mode 100644
index 00000000..b4accf64
--- /dev/null
+++ b/.env/.env.example
@@ -0,0 +1,11 @@
+# Website (Required)
+PUBLIC_WEBSITE_URL=
+
+# Analytics (Optional)
+PUBLIC_POSTHOG_KEY=
+
+# Discord Bot (Optional)
+DISCORD_CLIENT_ID=
+DISCORD_PUBLIC_KEY=
+DISCORD_CLIENT_SECRET=
+DISCORD_TOKEN=
diff --git a/apps/browser-extension/.env.development b/apps/browser-extension/.env.development
deleted file mode 100644
index 8a03b54c..00000000
--- a/apps/browser-extension/.env.development
+++ /dev/null
@@ -1,2 +0,0 @@
-# Website
-PLASMO_PUBLIC_WEBSITE_URL="http://localhost:3000"
diff --git a/apps/browser-extension/.env.example b/apps/browser-extension/.env.example
deleted file mode 100644
index c40b99a5..00000000
--- a/apps/browser-extension/.env.example
+++ /dev/null
@@ -1,5 +0,0 @@
-# Website
-PLASMO_PUBLIC_WEBSITE_URL=
-
-# PostHog
-PLASMO_PUBLIC_POSTHOG_KEY=
diff --git a/apps/browser-extension/package.json b/apps/browser-extension/package.json
index 9e9c28f9..1e7b00dc 100644
--- a/apps/browser-extension/package.json
+++ b/apps/browser-extension/package.json
@@ -13,20 +13,19 @@
},
"scripts": {
"check": "tsc --noEmit",
- "dev": "plasmo dev",
+ "dev": "use-env -p PLASMO -- plasmo dev",
"build:styles": "cp ../../packages/react/style.css ./src/style.css",
"build": "pnpm build:chrome",
- "build:chrome": "plasmo build --target=chrome-mv3 --zip",
- "package": "plasmo package"
+ "build:chrome": "use-env -p PLASMO -P -- plasmo build --target=chrome-mv3 --zip"
},
"dependencies": {
"@evaluate/components": "workspace:^",
"@evaluate/engine": "workspace:^",
- "@evaluate/env": "workspace:^",
"@evaluate/helpers": "workspace:^",
"@evaluate/hooks": "workspace:^",
- "@evaluate/style": "workspace:^",
"@evaluate/shapes": "workspace:^",
+ "@evaluate/style": "workspace:^",
+ "@t3-oss/env-core": "^0.11.1",
"framer-motion": "^11.12.0",
"lucide-react": "^0.338.0",
"posthog-js": "1.161.3",
diff --git a/apps/browser-extension/src/background/index.ts b/apps/browser-extension/src/background/index.ts
index a3b7bddd..c2c6744f 100644
--- a/apps/browser-extension/src/background/index.ts
+++ b/apps/browser-extension/src/background/index.ts
@@ -1,7 +1,7 @@
import { executeCode } from '@evaluate/engine/dist/execute';
import { searchRuntimes } from '@evaluate/engine/dist/runtimes';
import type { PartialRuntime } from '@evaluate/shapes';
-import { env } from '~env';
+import env from '~env';
import analytics from '~services/analytics';
chrome.action.setTitle({ title: 'Evaluate' });
@@ -33,7 +33,7 @@ chrome.action.onClicked.addListener(async () => {
});
chrome.tabs.create({
- url: env.PLASMO_PUBLIC_WEBSITE_URL,
+ url: `${env.PLASMO_PUBLIC_WEBSITE_URL}`,
});
});
diff --git a/apps/browser-extension/src/contents/_components/results-dialog.tsx b/apps/browser-extension/src/contents/_components/results-dialog.tsx
index fac1e07f..ac602f14 100644
--- a/apps/browser-extension/src/contents/_components/results-dialog.tsx
+++ b/apps/browser-extension/src/contents/_components/results-dialog.tsx
@@ -14,7 +14,7 @@ import { cn } from '@evaluate/helpers/dist/class';
import type { ExecuteResult, PartialRuntime } from '@evaluate/shapes';
import { ExternalLinkIcon, XIcon } from 'lucide-react';
import { useMemo } from 'react';
-import { env } from '~env';
+import env from '~env';
import { wrapCapture } from '~services/analytics';
export function ResultsCard(p: {
@@ -34,7 +34,7 @@ export function ResultsCard(p: {
className="mr-auto inline-flex items-center gap-2"
target="_blank"
rel="noreferrer noopener"
- href={env.PLASMO_PUBLIC_WEBSITE_URL}
+ href={`${env.PLASMO_PUBLIC_WEBSITE_URL}`}
>
!v.endsWith('/'), 'should not end with a slash'),
+ .transform((v) => new URL(v).freeze()),
PLASMO_PUBLIC_POSTHOG_KEY: z.string().min(1).optional(),
},
- variablesStrict: {
+ runtimeEnv: {
+ ...process.env,
PLASMO_PUBLIC_WEBSITE_URL: process.env.PLASMO_PUBLIC_WEBSITE_URL,
PLASMO_PUBLIC_POSTHOG_KEY: process.env.PLASMO_PUBLIC_POSTHOG_KEY,
},
-
- onValid(env) {
- if (!env.PLASMO_PUBLIC_POSTHOG_KEY)
- console.warn(
- 'Missing Posthog environment variable, analytics will be disabled.',
- );
- },
});
diff --git a/apps/browser-extension/src/services/analytics.ts b/apps/browser-extension/src/services/analytics.ts
index e6567bfb..1e10b0d2 100644
--- a/apps/browser-extension/src/services/analytics.ts
+++ b/apps/browser-extension/src/services/analytics.ts
@@ -1,7 +1,9 @@
import posthog from 'posthog-js';
-import { env } from '~env';
+import env from '~env';
-const enabled = env.PLASMO_PUBLIC_POSTHOG_KEY && env.PLASMO_PUBLIC_WEBSITE_URL;
+const enabled = Boolean(
+ env.PLASMO_PUBLIC_POSTHOG_KEY && env.PLASMO_PUBLIC_WEBSITE_URL,
+);
export default enabled ? posthog : null;
if (enabled) {
diff --git a/apps/discord-bot/package.json b/apps/discord-bot/package.json
index 4595bde9..1a62b3e4 100644
--- a/apps/discord-bot/package.json
+++ b/apps/discord-bot/package.json
@@ -6,6 +6,10 @@
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
+ },
+ "./env": {
+ "import": "./dist/env.js",
+ "types": "./dist/env.d.ts"
}
},
"scripts": {
@@ -16,8 +20,9 @@
"dependencies": {
"@buape/carbon": "0.0.0-beta-20250120130953",
"@evaluate/engine": "workspace:^",
- "@evaluate/env": "workspace:^",
+ "@evaluate/helpers": "workspace:^",
"@evaluate/shapes": "workspace:^",
+ "@t3-oss/env-core": "^0.11.1",
"@vercel/functions": "^1.5.1",
"posthog-node": "^4.3.1",
"zod": "3.22.4"
diff --git a/apps/discord-bot/src/components/open-evaluation-button.ts b/apps/discord-bot/src/components/open-evaluation-button.ts
index 1abb908c..2d239f17 100644
--- a/apps/discord-bot/src/components/open-evaluation-button.ts
+++ b/apps/discord-bot/src/components/open-evaluation-button.ts
@@ -1,10 +1,10 @@
import { LinkButton } from '@buape/carbon';
-import { env } from '~/env';
+import env from '~/env';
import { resolveEmoji } from '~/utilities/resolve-emoji';
export class OpenEvaluationButton extends LinkButton {
label = 'Open Evaluation';
- url = env.WEBSITE_URL;
+ url = `${env.WEBSITE_URL}`;
emoji = resolveEmoji('globe', true);
public constructor(url: string) {
diff --git a/apps/discord-bot/src/env.ts b/apps/discord-bot/src/env.ts
index 86cd9a23..dc496c06 100644
--- a/apps/discord-bot/src/env.ts
+++ b/apps/discord-bot/src/env.ts
@@ -1,10 +1,13 @@
-import { validateEnv } from '@evaluate/env/validator';
+import { URL } from '@evaluate/helpers/url';
+import { createEnv } from '@t3-oss/env-core';
import { z } from 'zod';
-export const env = validateEnv({
+export default createEnv({
server: {
- ENV: z.enum(['development', 'production']),
- WEBSITE_URL: z.string().url(),
+ WEBSITE_URL: z
+ .string()
+ .url()
+ .transform((v) => new URL(v).freeze()),
POSTHOG_KEY: z.string().optional(),
DISCORD_TOKEN: z.string().min(1).optional(),
DISCORD_PUBLIC_KEY: z.string().min(1).optional(),
@@ -12,29 +15,8 @@ export const env = validateEnv({
DISCORD_CLIENT_SECRET: z.string().min(1).optional(),
},
- variablesStrict: {
- ENV: process.env.NODE_ENV,
- WEBSITE_URL: process.env.WEBSITE_URL || `https://${process.env.VERCEL_URL}`,
- POSTHOG_KEY: process.env.POSTHOG_KEY || process.env.NEXT_PUBLIC_POSTHOG_KEY,
- DISCORD_TOKEN: process.env.DISCORD_TOKEN,
- DISCORD_PUBLIC_KEY: process.env.DISCORD_PUBLIC_KEY,
- DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID,
- DISCORD_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET,
- },
-
- onValid(env) {
- if (
- !env.DISCORD_TOKEN ||
- !env.DISCORD_PUBLIC_KEY ||
- !env.DISCORD_CLIENT_ID ||
- !env.DISCORD_CLIENT_SECRET
- )
- console.warn(
- 'Missing Discord bot environment variables, it will be disabled.',
- );
- if (!env.POSTHOG_KEY)
- console.warn(
- 'Missing Posthog environment variable, analytics will be disabled.',
- );
+ runtimeEnv: {
+ WEBSITE_URL: `https://${process.env.VERCEL_URL}`,
+ ...process.env,
},
});
diff --git a/apps/discord-bot/src/handlers/evaluate.ts b/apps/discord-bot/src/handlers/evaluate.ts
index c36b66d9..abe13a90 100644
--- a/apps/discord-bot/src/handlers/evaluate.ts
+++ b/apps/discord-bot/src/handlers/evaluate.ts
@@ -14,7 +14,7 @@ import {
import type { ExecuteResult, PartialRuntime } from '@evaluate/shapes';
import { EditEvaluationButton } from '~/components/edit-evaluation-button';
import { OpenEvaluationButton } from '~/components/open-evaluation-button';
-import { env } from '~/env';
+import env from '~/env';
import analytics from '~/services/analytics';
import { codeBlock } from '~/utilities/discord-formatting';
diff --git a/apps/discord-bot/src/index.ts b/apps/discord-bot/src/index.ts
index baa14b17..aec6651a 100644
--- a/apps/discord-bot/src/index.ts
+++ b/apps/discord-bot/src/index.ts
@@ -1,23 +1,7 @@
-import { Client, InteractionType } from '@buape/carbon';
-import { EvaluateCommand } from './commands/evaluate';
-import { env } from './env';
+import { InteractionType } from '@buape/carbon';
+import env from './env';
import analytics from './services/analytics';
-
-const client =
- env.DISCORD_TOKEN &&
- env.DISCORD_CLIENT_ID &&
- env.DISCORD_PUBLIC_KEY &&
- new Client(
- {
- baseUrl: 'unused',
- clientId: env.DISCORD_CLIENT_ID,
- publicKey: env.DISCORD_PUBLIC_KEY,
- token: env.DISCORD_TOKEN,
- deploySecret: 'unused',
- requestOptions: { queueRequests: false },
- },
- [new EvaluateCommand()],
- );
+import client from './services/client';
export default async function handler(request: Request) {
if (!client) return new Response('X|', { status: 503 });
diff --git a/apps/discord-bot/src/services/analytics.ts b/apps/discord-bot/src/services/analytics.ts
index 6f928f0f..08501bb6 100644
--- a/apps/discord-bot/src/services/analytics.ts
+++ b/apps/discord-bot/src/services/analytics.ts
@@ -1,8 +1,14 @@
import { PostHog } from 'posthog-node';
-import { env } from '~/env';
+import env from '~/env';
-export default env.POSTHOG_KEY
- ? new PostHog(env.POSTHOG_KEY, {
+const enabled = Boolean(env.POSTHOG_KEY);
+if (!enabled)
+ console.warn(
+ 'Missing Posthog environment variable, analytics will be disabled.',
+ );
+
+export default enabled
+ ? new PostHog(env.POSTHOG_KEY!, {
host: 'https://app.posthog.com/',
flushAt: 1,
flushInterval: 0,
diff --git a/apps/discord-bot/src/services/client.ts b/apps/discord-bot/src/services/client.ts
new file mode 100644
index 00000000..056c5d42
--- /dev/null
+++ b/apps/discord-bot/src/services/client.ts
@@ -0,0 +1,25 @@
+import { Client } from '@buape/carbon';
+import { EvaluateCommand } from '~/commands/evaluate';
+import env from '~/env';
+
+const enabled = Boolean(
+ env.DISCORD_CLIENT_ID && env.DISCORD_PUBLIC_KEY && env.DISCORD_TOKEN,
+);
+if (!enabled)
+ console.warn(
+ 'Missing Discord bot environment variables, bot will be disabled.',
+ );
+
+export default enabled
+ ? new Client(
+ {
+ baseUrl: 'unused',
+ clientId: env.DISCORD_CLIENT_ID!,
+ publicKey: env.DISCORD_PUBLIC_KEY!,
+ token: env.DISCORD_TOKEN!,
+ deploySecret: 'unused',
+ requestOptions: { queueRequests: false },
+ },
+ [new EvaluateCommand()],
+ )
+ : null;
diff --git a/apps/discord-bot/tsup.config.ts b/apps/discord-bot/tsup.config.ts
index 253c619a..f2b32be9 100644
--- a/apps/discord-bot/tsup.config.ts
+++ b/apps/discord-bot/tsup.config.ts
@@ -1,7 +1,7 @@
import { defineConfig } from 'tsup';
export default defineConfig({
- entry: ['src/index.ts'],
+ entry: ['src/index.ts', 'src/env.ts'],
format: 'esm',
dts: true,
});
diff --git a/apps/website/.env.development b/apps/website/.env.development
deleted file mode 100644
index 2598d2bc..00000000
--- a/apps/website/.env.development
+++ /dev/null
@@ -1,2 +0,0 @@
-# Website
-WEBSITE_URL="http://localhost:3000"
diff --git a/apps/website/.env.example b/apps/website/.env.example
deleted file mode 100644
index 6a956dc2..00000000
--- a/apps/website/.env.example
+++ /dev/null
@@ -1,12 +0,0 @@
-# Website
-WEBSITE_URL=
-
-# PostHog
-POSTHOG_KEY=
-NEXT_PUBLIC_POSTHOG_KEY=$POSTHOG_KEY
-
-# Discord Bot
-DISCORD_CLIENT_ID=
-DISCORD_PUBLIC_KEY=
-DISCORD_CLIENT_SECRET=
-DISCORD_TOKEN=
diff --git a/apps/website/package.json b/apps/website/package.json
index 4d022e81..243b5cba 100644
--- a/apps/website/package.json
+++ b/apps/website/package.json
@@ -4,21 +4,21 @@
"type": "module",
"scripts": {
"check": "tsc --noEmit",
- "build": "next build --no-lint",
- "start": "next start",
- "dev": "next dev --turbo"
+ "build": "use-env -p NEXT -P -- next build --no-lint",
+ "start": "use-env -p NEXT -P -- next start",
+ "dev": "use-env -p NEXT -- next dev --turbo"
},
"dependencies": {
"@codemirror/commands": "^6.7.1",
"@codemirror/view": "^6.35.0",
"@evaluate/components": "workspace:^",
"@evaluate/engine": "workspace:^",
- "@evaluate/env": "workspace:^",
"@evaluate/helpers": "workspace:^",
"@evaluate/hooks": "workspace:^",
- "@evaluate/style": "workspace:^",
"@evaluate/shapes": "workspace:^",
+ "@evaluate/style": "workspace:^",
"@hookform/resolvers": "^3.9.1",
+ "@t3-oss/env-nextjs": "^0.11.1",
"@tanstack/react-query": "^5.62.0",
"@tanstack/react-query-devtools": "^5.62.0",
"@uiw/codemirror-extensions-langs": "^4.23.6",
diff --git a/apps/website/src/app/(editor)/playgrounds/[playground]/content.tsx b/apps/website/src/app/(editor)/playgrounds/[playground]/content.tsx
index 7780b5f6..cdf2b8c8 100644
--- a/apps/website/src/app/(editor)/playgrounds/[playground]/content.tsx
+++ b/apps/website/src/app/(editor)/playgrounds/[playground]/content.tsx
@@ -22,7 +22,7 @@ export default function EditorContent(p: { runtime: Runtime }) {
return (
diff --git a/apps/website/src/app/metadata.ts b/apps/website/src/app/metadata.ts
index 30c544c8..f160714e 100644
--- a/apps/website/src/app/metadata.ts
+++ b/apps/website/src/app/metadata.ts
@@ -1,6 +1,6 @@
import _ from 'lodash';
import type { Metadata } from 'next/types';
-import { env } from '~/env';
+import env from '~/env';
export function generateBaseMetadata(
pathname: string,
diff --git a/apps/website/src/app/sitemap.ts b/apps/website/src/app/sitemap.ts
index cb689b4c..3fd8cfaf 100644
--- a/apps/website/src/app/sitemap.ts
+++ b/apps/website/src/app/sitemap.ts
@@ -2,14 +2,14 @@ import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fetchRuntimes } from '@evaluate/engine/runtimes';
import type { MetadataRoute } from 'next/types';
-import { env } from '~/env';
+import env from '~/env';
interface RoutesManifest {
staticRoutes: { page: string }[];
dynamicRoutes: { page: string }[];
}
-async function loadStaticPaths(url: string): Promise {
+async function loadStaticPaths(url: URL): Promise {
const manifestPath = join(process.cwd(), '.next', 'routes-manifest.json');
const manifest = await readFile(manifestPath, 'utf8')
.then((c) => JSON.parse(c) as RoutesManifest)
@@ -23,7 +23,7 @@ async function loadStaticPaths(url: string): Promise {
}));
}
-async function loadDynamicPaths(url: string): Promise {
+async function loadDynamicPaths(url: URL): Promise {
const runtimes = await fetchRuntimes();
return runtimes.map((r) => ({
url: `${url}/playgrounds/${r.id}`,
diff --git a/apps/website/src/env.ts b/apps/website/src/env.ts
index 201185c9..bc435ef6 100644
--- a/apps/website/src/env.ts
+++ b/apps/website/src/env.ts
@@ -1,27 +1,25 @@
-import { validateEnv } from '@evaluate/env/validator';
+import { URL } from '@evaluate/helpers/url';
+import { createEnv } from '@t3-oss/env-nextjs';
+import { vercel } from '@t3-oss/env-nextjs/presets';
+import discordEnv from 'discord-bot/env';
import { z } from 'zod';
-export const env = validateEnv({
+export default createEnv({
+ extends: [discordEnv, vercel()],
+
server: {
WEBSITE_URL: z
.string()
.url()
- .refine((v) => !v.endsWith('/'), 'should not end with a slash'),
+ .transform((v) => new URL(v).freeze()),
},
- prefix: 'NEXT_PUBLIC_',
client: {
NEXT_PUBLIC_POSTHOG_KEY: z.string().min(1).optional(),
},
- variablesStrict: {
- WEBSITE_URL: process.env.WEBSITE_URL || `https://${process.env.VERCEL_URL}`,
+ runtimeEnv: {
+ WEBSITE_URL: `https://${process.env.VERCEL_URL}`,
+ ...process.env,
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
},
-
- onValid(env) {
- if (!env.NEXT_PUBLIC_POSTHOG_KEY)
- console.warn(
- 'Missing Posthog environment variable, analytics will be disabled.',
- );
- },
});
diff --git a/apps/website/src/services/analytics.ts b/apps/website/src/services/analytics.ts
index a7799984..ce66ef2f 100644
--- a/apps/website/src/services/analytics.ts
+++ b/apps/website/src/services/analytics.ts
@@ -1,10 +1,11 @@
import posthog from 'posthog-js';
-import { env } from '~/env';
+import env from '~/env';
-const enabled =
+const enabled = Boolean(
typeof window !== 'undefined' &&
- !window.location.origin.endsWith('.vercel.app') &&
- env.NEXT_PUBLIC_POSTHOG_KEY;
+ !window.location.origin.endsWith('.vercel.app') &&
+ env.NEXT_PUBLIC_POSTHOG_KEY,
+);
export default enabled ? posthog : null;
if (enabled) {
diff --git a/package.json b/package.json
index ff240101..bad726fe 100644
--- a/package.json
+++ b/package.json
@@ -15,10 +15,11 @@
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
+ "@evaluate/scripts": "workspace:^",
"@types/node": "^22.10.7",
+ "husky": "^9.1.7",
"resolve-tspaths": "^0.8.23",
"tsup": "^8.3.5",
- "husky": "^9.1.7",
"tsx": "^4.19.2",
"turbo": "^2.3.3",
"typescript": "^5.7.3"
diff --git a/packages/env/package.json b/packages/env/package.json
deleted file mode 100644
index d9b64fc9..00000000
--- a/packages/env/package.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "name": "@evaluate/env",
- "version": "1.0.0",
- "type": "module",
- "main": "./dist/validator.js",
- "module": "./dist/validator.js",
- "exports": {
- ".": {
- "import": "./dist/validator.js",
- "types": "./dist/validator.d.ts"
- },
- "./loader": {
- "import": "./dist/loader.js",
- "types": "./dist/loader.d.ts"
- },
- "./validator": {
- "import": "./dist/validator.js",
- "types": "./dist/validator.d.ts"
- }
- },
- "scripts": {
- "check": "tsc --noEmit",
- "build": "tsup"
- },
- "dependencies": {
- "dotenv": "^16.4.5",
- "dotenv-expand": "^11.0.7",
- "zod": "3.22.4"
- }
-}
diff --git a/packages/env/src/loader.ts b/packages/env/src/loader.ts
deleted file mode 100644
index f18f161a..00000000
--- a/packages/env/src/loader.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as fs from 'node:fs';
-import * as path from 'node:path';
-import * as dotenv from 'dotenv';
-import { expand as dotenvExpand } from 'dotenv-expand';
-
-/**
- * Load the environment variables from the environment files into the process.
- * @returns process.{@link process.env env}
- */
-export function loadEnv() {
- insertVariables(readEnv());
- return process.env;
-}
-
-/**
- * Read the environment variables from the environment files.
- * @returns the environment variables
- */
-export function readEnv() {
- const variables: Record = {};
- const root = process.env.ORIGINAL_DIR ?? '.';
-
- for (const file of getListOfEnvFiles().reverse()) {
- const location = path.join(root, file);
- if (!fs.existsSync(location)) continue;
-
- const contents = fs.readFileSync(location, 'utf8');
- const parsed = extractVariablesFromContents(contents);
- for (const [key, value] of Object.entries(parsed ?? {}))
- if (value !== undefined) variables[key] = value;
- }
-
- return expandVariables(variables);
-}
-
-/**
- * Insert the variables into the process environment.
- * @param variables the variables to insert
- */
-export function insertVariables(variables: Record) {
- for (const [key, value] of Object.entries(variables))
- process.env[key] ??= value;
-}
-
-/**
- * Get a list of the environment files to look for, in order of priority.
- * @returns the list of environment files
- */
-function getListOfEnvFiles() {
- const mode = process.env.NODE_ENV || 'development';
- return [`.env.${mode}.local`, '.env.local', `.env.${mode}`, '.env'];
-}
-
-/**
- * Extract the variables from a string.
- * @param content the content to extract the variables from
- * @returns the extracted variables
- */
-function extractVariablesFromContents(content: string) {
- return dotenv.parse(content);
-}
-
-function expandVariables(parsed: Record) {
- return dotenvExpand({ parsed, processEnv: parsed }).parsed ?? {};
-}
diff --git a/packages/env/src/validator.ts b/packages/env/src/validator.ts
deleted file mode 100644
index c814f3bd..00000000
--- a/packages/env/src/validator.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import { z } from 'zod';
-
-type EmptyRecord = Record;
-
-type Simplify = {
- [P in keyof T]: T[P];
-} & {};
-
-//
-
-interface ListenerOptions> {
- onValid?: (env: TVariables) => void;
- onInvalid?: (error: Zod.ZodError) => void;
- onDisallowed?: (name: keyof TVariables) => void;
-}
-
-//
-
-interface ServerOptions<
- TPrefix extends string | undefined,
- TShape extends Record,
-> {
- server?: {
- [TKey in keyof TShape]: TPrefix extends undefined
- ? TShape[TKey]
- : TPrefix extends ''
- ? TShape[TKey]
- : TKey extends `${TPrefix}${string}`
- ? never
- : TShape[TKey];
- };
-}
-
-interface ClientOptions<
- TPrefix extends string | undefined,
- TShape extends Record,
-> {
- prefix?: TPrefix;
- client?: {
- [TKey in keyof TShape]: TKey extends `${TPrefix}${string}`
- ? TShape[TKey]
- : never;
- };
-}
-
-//
-
-interface LooseVariableOptions {
- variables: Record;
-}
-
-interface StrictVariableOptions {
- variablesStrict: Record;
-}
-
-//
-
-type Options<
- TPrefix extends string | undefined,
- TServerShape extends Record = EmptyRecord,
- TClientShape extends Record = EmptyRecord,
- TVariables extends Simplify<
- z.infer> & z.infer>
- > = Simplify<
- z.infer> & z.infer>
- >,
-> = ListenerOptions &
- ServerOptions &
- ClientOptions &
- (LooseVariableOptions | StrictVariableOptions);
-
-//
-
-/**
- * Validate the environment variables and return a proxy object.
- * @param options the options to use when validating the environment variables
- * @returns a proxy object that can be used to access the environment variables
- */
-export function validateEnv<
- TPrefix extends string | undefined,
- TServerShape extends Record = EmptyRecord,
- TClientShape extends Record = EmptyRecord,
- TVariables extends Simplify<
- z.infer> & z.infer>
- > = Simplify<
- z.infer> & z.infer>
- >,
->(
- options: Options,
-): TVariables {
- const variables =
- 'variablesStrict' in options ? options.variablesStrict : options.variables;
- for (const [key, value] of Object.entries(variables))
- if (value === '') delete variables[key];
-
- const clientSchema = z.object(options.client ?? {});
- const serverSchema = z.object(options.server ?? {}).merge(clientSchema);
-
- const isServer = typeof window === 'undefined';
- const parseResult = isServer
- ? serverSchema.safeParse(variables)
- : clientSchema.safeParse(variables);
-
- function onValid(env: TVariables) {
- return options.onValid?.(env);
- }
-
- function onInvalid(error: z.ZodError) {
- if (options.onInvalid) return options.onInvalid(error);
- console.error(
- '❌ Invalid environment variables:',
- error.flatten().fieldErrors,
- );
- throw new Error('Invalid environment variables');
- }
-
- function isClientVariable(name: keyof TVariables) {
- return (
- String(name).startsWith(options.prefix ?? '') &&
- name in (options.client ?? {})
- );
- }
-
- if (parseResult.success) {
- onValid(parseResult.data as TVariables);
- return buildEnvProxy(
- parseResult.data as TVariables, //
- { isClientVariable, ...options },
- );
- } else {
- onInvalid(parseResult.error);
- }
-
- return {} as never;
-}
-
-/**
- * Takes a record of environment variables and returns a proxy object.
- * @param variables the environment variables to use
- * @param options the options to use when creating the proxy object
- * @returns a proxy object that can be used to access the environment variables
- */
-function buildEnvProxy>(
- variables: T,
- options: {
- isClientVariable: (name: keyof T) => boolean;
- onDisallowed?: (name: keyof T) => void;
- },
-): T {
- function isDisallowed(name: keyof T) {
- return typeof window !== 'undefined' && !options.isClientVariable(name);
- }
-
- function onDisallowed(name: keyof T) {
- if (options.onDisallowed) return options.onDisallowed(name);
- throw new Error(
- '❌ Attempted to access a server-side environment variable from the client',
- );
- }
-
- return new Proxy(variables, {
- get(target, name: string) {
- if (typeof name !== 'string') return Reflect.get(target, name);
- if (isDisallowed(name)) return onDisallowed(name);
- return Reflect.get(target, name);
- },
- });
-}
diff --git a/packages/env/tsconfig.json b/packages/env/tsconfig.json
deleted file mode 100644
index f4318e0e..00000000
--- a/packages/env/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "compilerOptions": { "baseUrl": ".", "paths": { "~/*": ["src/*"] } }
-}
diff --git a/packages/env/tsup.config.ts b/packages/env/tsup.config.ts
deleted file mode 100644
index 66de8558..00000000
--- a/packages/env/tsup.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { defineConfig } from 'tsup';
-
-export default defineConfig({
- entry: ['src/*.ts'],
- format: 'esm',
- dts: true,
-});
diff --git a/packages/scripts/bin/_logger.js b/packages/scripts/bin/_logger.js
new file mode 100644
index 00000000..7ee30b73
--- /dev/null
+++ b/packages/scripts/bin/_logger.js
@@ -0,0 +1,16 @@
+const colours = {
+ green: (_) => `\x1b[32m${_}\x1b[0m`,
+ yellow: (_) => `\x1b[33m${_}\x1b[0m`,
+ red: (_) => `\x1b[31m${_}\x1b[0m`,
+ bold: (_) => `\x1b[1m${_}\x1b[0m`,
+ italic: (_) => `\x1b[3m${_}\x1b[0m`,
+ dim: (_) => `\x1b[2m${_}\x1b[0m`,
+};
+
+const logger = {
+ info: (_) => console.info(`${colours.bold(colours.green(' >_'))} ${_}`),
+ warn: (_) => console.warn(colours.yellow(`${colours.bold(' >_')} ${_}`)),
+ error: (_) => console.error(colours.red(`${colours.bold(' >_')} ${_}`)),
+};
+
+export { logger, colours };
diff --git a/packages/scripts/bin/run-in b/packages/scripts/bin/run-in
new file mode 100644
index 00000000..0abe6169
--- /dev/null
+++ b/packages/scripts/bin/run-in
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+import { exec, spawn } from 'node:child_process';
+import { sep } from 'node:path';
+import { promisify } from 'node:util';
+import { createCommand } from 'commander';
+const execAsync = promisify(exec);
+
+const program = createCommand('run-in')
+ .arguments(' ')
+ .parse(process.argv);
+
+const packagePaths = await execAsync('pnpm m ls --depth -1 --porcelain') //
+ .then(({ stdout }) => stdout.split('\n'));
+const packages = packagePaths //
+ .map((path) => ({ path, name: path.split(sep).pop() ?? '' }));
+
+const targetPackage = packages.find((p) => p.name === program.args[0]);
+const commandToRun = program.args
+ .slice(1)
+ .map((a) => `"${a}"`)
+ .join(' ');
+
+spawn(commandToRun, {
+ stdio: 'inherit',
+ shell: process.env.SHELL,
+ cwd: targetPackage.path,
+});
diff --git a/packages/scripts/bin/use-env b/packages/scripts/bin/use-env
new file mode 100644
index 00000000..659d3b35
--- /dev/null
+++ b/packages/scripts/bin/use-env
@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+
+import nextEnv from '@next/env';
+import { spawn } from 'cross-spawn';
+const { loadEnvConfig } = nextEnv;
+import { join as joinPath, dirname as pathDirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { createCommand } from 'commander';
+import { colours, logger } from './_logger.js';
+
+// Reorder arguments if there are two `--` separators
+const separatorCount = process.argv.filter((a) => a === '--').length;
+if (separatorCount === 2) {
+ const firstIndex = process.argv.indexOf('--');
+ const secondIndex = process.argv.indexOf('--', firstIndex + 1);
+ const secondGroup = process.argv.splice(secondIndex + 1);
+ process.argv.splice(firstIndex, 0, ...secondGroup);
+}
+
+const program = createCommand('use-env')
+ .option(
+ '-p, --public-prefix [prefix]',
+ 'Append a prefix to each public variable',
+ )
+ .option(
+ '-P, --prod',
+ 'Load production environment',
+ process.env.NODE_ENV === 'production',
+ )
+ .argument('', 'The command to run')
+ .parse(process.argv);
+
+const publicPrefix = program.opts().publicPrefix ?? '';
+const isProduction = program.opts().prod;
+const commandToRun = program.args.map((a) => `"${a}"`).join(' ');
+const thisDirname = pathDirname(fileURLToPath(import.meta.url));
+const targetDirectory = joinPath(thisDirname, '../../../.env');
+
+// Load the environment variables
+const { loadedEnvFiles } = loadEnvConfig(targetDirectory, !isProduction);
+
+// Handle public variables with optional prefix
+for (const key in process.env) {
+ if (key.startsWith('PUBLIC_')) {
+ process.env[key.slice(7)] = process.env[key];
+ if (publicPrefix) {
+ process.env[`${publicPrefix}_${key}`] = process.env[key];
+ delete process.env[key];
+ }
+ }
+}
+
+const loadedFrom = loadedEnvFiles.map((f) => f.path).join(' ');
+if (loadedFrom) logger.info(`Environment: ${colours.italic(loadedFrom)}`);
+else logger.info('No Environment');
+process.env['>_'] = true;
+
+const child = spawn(commandToRun, {
+ stdio: 'inherit',
+ shell: process.env.SHELL || '/bin/bash',
+ env: process.env,
+});
+child.on('exit', process.exit);
diff --git a/packages/scripts/package.json b/packages/scripts/package.json
new file mode 100644
index 00000000..be75c895
--- /dev/null
+++ b/packages/scripts/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@evaluate/scripts",
+ "version": "1.0.0",
+ "type": "module",
+ "bin": {
+ "run-in": "./bin/run-in",
+ "use-env": "./bin/use-env"
+ },
+ "dependencies": {
+ "@next/env": "^15.1.4",
+ "commander": "^13.0.0",
+ "cross-spawn": "^7.0.6"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 82abf80e..49c07eb5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -19,6 +19,9 @@ importers:
'@biomejs/biome':
specifier: 1.9.4
version: 1.9.4
+ '@evaluate/scripts':
+ specifier: workspace:^
+ version: link:packages/scripts
'@types/node':
specifier: ^22.10.7
version: 22.10.7
@@ -49,9 +52,6 @@ importers:
'@evaluate/engine':
specifier: workspace:^
version: link:../../packages/engine
- '@evaluate/env':
- specifier: workspace:^
- version: link:../../packages/env
'@evaluate/helpers':
specifier: workspace:^
version: link:../../packages/helpers
@@ -64,6 +64,9 @@ importers:
'@evaluate/style':
specifier: workspace:^
version: link:../../packages/style
+ '@t3-oss/env-core':
+ specifier: ^0.11.1
+ version: 0.11.1(typescript@5.2.2)(zod@3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq))
framer-motion:
specifier: ^11.12.0
version: 11.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -110,12 +113,15 @@ importers:
'@evaluate/engine':
specifier: workspace:^
version: link:../../packages/engine
- '@evaluate/env':
+ '@evaluate/helpers':
specifier: workspace:^
- version: link:../../packages/env
+ version: link:../../packages/helpers
'@evaluate/shapes':
specifier: workspace:^
version: link:../../packages/shapes
+ '@t3-oss/env-core':
+ specifier: ^0.11.1
+ version: 0.11.1(typescript@5.7.3)(zod@3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq))
'@vercel/functions':
specifier: ^1.5.1
version: 1.5.1
@@ -140,9 +146,6 @@ importers:
'@evaluate/engine':
specifier: workspace:^
version: link:../../packages/engine
- '@evaluate/env':
- specifier: workspace:^
- version: link:../../packages/env
'@evaluate/helpers':
specifier: workspace:^
version: link:../../packages/helpers
@@ -158,6 +161,9 @@ importers:
'@hookform/resolvers':
specifier: ^3.9.1
version: 3.9.1(react-hook-form@7.53.2(react@18.3.1))
+ '@t3-oss/env-nextjs':
+ specifier: ^0.11.1
+ version: 0.11.1(typescript@5.7.3)(zod@3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq))
'@tanstack/react-query':
specifier: ^5.62.0
version: 5.62.0(react@18.3.1)
@@ -375,18 +381,6 @@ importers:
specifier: ^2.0.3
version: 2.0.3
- packages/env:
- dependencies:
- dotenv:
- specifier: ^16.4.5
- version: 16.4.5
- dotenv-expand:
- specifier: ^11.0.7
- version: 11.0.7
- zod:
- specifier: 3.22.4
- version: 3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq)
-
packages/helpers:
dependencies:
class-variance-authority:
@@ -419,6 +413,18 @@ importers:
specifier: ^19.0.4
version: 19.0.7
+ packages/scripts:
+ dependencies:
+ '@next/env':
+ specifier: ^15.1.4
+ version: 15.1.5
+ commander:
+ specifier: ^13.0.0
+ version: 13.1.0
+ cross-spawn:
+ specifier: ^7.0.6
+ version: 7.0.6
+
packages/shapes:
dependencies:
zod:
@@ -1431,6 +1437,9 @@ packages:
'@next/env@14.2.18':
resolution: {integrity: sha512-2vWLOUwIPgoqMJKG6dt35fVXVhgM09tw4tK3/Q34GFXDrfiHlG7iS33VA4ggnjWxjiz9KV5xzfsQzJX6vGAekA==}
+ '@next/env@15.1.5':
+ resolution: {integrity: sha512-jg8ygVq99W3/XXb9Y6UQsritwhjc+qeiO7QrGZRYOfviyr/HcdnhdBQu4gbp2rBIh2ZyBYTBMWbPw3JSCb0GHw==}
+
'@next/swc-darwin-arm64@14.2.18':
resolution: {integrity: sha512-tOBlDHCjGdyLf0ube/rDUs6VtwNOajaWV+5FV/ajPgrvHeisllEdymY/oDgv2cx561+gJksfMUtqf8crug7sbA==}
engines: {node: '>= 10'}
@@ -3082,6 +3091,24 @@ packages:
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
engines: {node: '>=14.16'}
+ '@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
+
'@tanstack/query-core@5.62.0':
resolution: {integrity: sha512-sx38bGrqF9bop92AXOvzDr0L9fWDas5zXdPglxa9cuqeVSWS7lY6OnVyl/oodfXjgOGRk79IfCpgVmxrbHuFHg==}
@@ -3575,6 +3602,10 @@ packages:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
+ commander@13.1.0:
+ resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
+ engines: {node: '>=18'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -3755,10 +3786,6 @@ packages:
resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
engines: {node: '>=12'}
- dotenv-expand@11.0.7:
- resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
- engines: {node: '>=12'}
-
dotenv-expand@5.1.0:
resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==}
@@ -3766,10 +3793,6 @@ packages:
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
engines: {node: '>=12'}
- dotenv@16.4.5:
- resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
- engines: {node: '>=12'}
-
dotenv@7.0.0:
resolution: {integrity: sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==}
engines: {node: '>=6'}
@@ -6651,6 +6674,8 @@ snapshots:
'@next/env@14.2.18': {}
+ '@next/env@15.1.5': {}
+
'@next/swc-darwin-arm64@14.2.18':
optional: true
@@ -8612,6 +8637,25 @@ snapshots:
dependencies:
defer-to-connect: 2.0.1
+ '@t3-oss/env-core@0.11.1(typescript@5.2.2)(zod@3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq))':
+ dependencies:
+ zod: 3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq)
+ optionalDependencies:
+ typescript: 5.2.2
+
+ '@t3-oss/env-core@0.11.1(typescript@5.7.3)(zod@3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq))':
+ dependencies:
+ zod: 3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq)
+ optionalDependencies:
+ typescript: 5.7.3
+
+ '@t3-oss/env-nextjs@0.11.1(typescript@5.7.3)(zod@3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq))':
+ dependencies:
+ '@t3-oss/env-core': 0.11.1(typescript@5.7.3)(zod@3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq))
+ zod: 3.22.4(patch_hash=j5rlizeyx7cjhu35u4l53omnrq)
+ optionalDependencies:
+ typescript: 5.7.3
+
'@tanstack/query-core@5.62.0': {}
'@tanstack/query-devtools@5.61.4': {}
@@ -9161,6 +9205,8 @@ snapshots:
commander@12.1.0: {}
+ commander@13.1.0: {}
+
commander@2.20.3:
optional: true
@@ -9327,16 +9373,10 @@ snapshots:
dotenv-expand@10.0.0: {}
- dotenv-expand@11.0.7:
- dependencies:
- dotenv: 16.4.5
-
dotenv-expand@5.1.0: {}
dotenv@16.3.1: {}
- dotenv@16.4.5: {}
-
dotenv@7.0.0: {}
duplexer@0.1.2: {}