diff --git a/apps/web/.env.development.example b/apps/web/.env.development.example index 6b390eb..fac15d7 100644 --- a/apps/web/.env.development.example +++ b/apps/web/.env.development.example @@ -4,7 +4,6 @@ # Drizzle / libSQL (local sqlite) DATABASE_URL=file:./local.db TURSO_AUTH_TOKEN= -NEXT_PUBLIC_APP_URL=http://localhost:3000 BETTER_AUTH_SECRET=dev-secret-please-change-dev-secret-32ch # OAuth (optional) diff --git a/apps/web/.env.production.example b/apps/web/.env.production.example index cbd4570..313ae1c 100644 --- a/apps/web/.env.production.example +++ b/apps/web/.env.production.example @@ -4,7 +4,6 @@ # Drizzle / libSQL (Turso) DATABASE_URL=libsql://-.turso.io TURSO_AUTH_TOKEN= -NEXT_PUBLIC_APP_URL=https:// BETTER_AUTH_SECRET= # OAuth (optional) diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 9c2e377..e562592 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -2,21 +2,6 @@ import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare'; initOpenNextCloudflareForDev(); -const appUrl = process.env.NEXT_PUBLIC_APP_URL - ? new URL(process.env.NEXT_PUBLIC_APP_URL) - : null; - -const appImageRemotePatterns = appUrl - ? [ - { - protocol: appUrl.protocol.replace(':', ''), - hostname: appUrl.hostname, - port: appUrl.port, - pathname: '/api/files/**', - }, - ] - : []; - /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, @@ -30,7 +15,6 @@ const nextConfig = { protocol: 'http', hostname: 'localhost', }, - ...appImageRemotePatterns, ], }, transpilePackages: [ diff --git a/apps/web/package.json b/apps/web/package.json index 1ee7bcd..56ce53f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,13 +4,13 @@ "private": true, "type": "module", "scripts": { - "build": "next build", - "build:worker": "opennextjs-cloudflare build", + "build": "SKIP_ENV_VALIDATION=true next build", + "build:worker": "SKIP_ENV_VALIDATION=true opennextjs-cloudflare build", "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts", - "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy", + "deploy": "SKIP_ENV_VALIDATION=true opennextjs-cloudflare build && opennextjs-cloudflare deploy", "dev": "next dev -p 3000", "lint": "eslint . --cache --max-warnings 0", - "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview", + "preview": "SKIP_ENV_VALIDATION=true opennextjs-cloudflare build && opennextjs-cloudflare preview", "start": "next start", "typecheck": "tsc --noEmit --tsBuildInfoFile .tsbuildinfo" }, diff --git a/apps/web/src/app/(landing)/layout.tsx b/apps/web/src/app/(landing)/layout.tsx index 78cb235..b4ea87e 100644 --- a/apps/web/src/app/(landing)/layout.tsx +++ b/apps/web/src/app/(landing)/layout.tsx @@ -10,9 +10,9 @@ async function LandingPageLayout({ children }: { children: ReactNode }) { const databaseUrl = process.env['DATABASE_URL']; const hasSessionRuntimeEnv = !!databaseUrl && - (!databaseUrl.startsWith('libsql://') || !!process.env['TURSO_AUTH_TOKEN']) && + (!databaseUrl.startsWith('libsql://') || + !!process.env['TURSO_AUTH_TOKEN']) && !!process.env['BETTER_AUTH_SECRET'] && - !!process.env['NEXT_PUBLIC_APP_URL'] && !!process.env['ALLOW_SIGNIN_SIGNUP']; let isLoggedIn = false; diff --git a/apps/web/src/app/(main)/dashboard/page.tsx b/apps/web/src/app/(main)/dashboard/page.tsx index 74df8e4..48aa29a 100644 --- a/apps/web/src/app/(main)/dashboard/page.tsx +++ b/apps/web/src/app/(main)/dashboard/page.tsx @@ -1,8 +1,6 @@ import * as React from 'react'; import { type Metadata } from 'next'; -import { env } from '@formbase/env'; - import { api } from '~/lib/trpc/server'; import { Forms } from './_components/forms'; @@ -10,7 +8,6 @@ import { CreateFormDialog } from './_components/new-form-dialog'; import { FormsSkeleton } from './_components/posts-skeleton'; export const metadata: Metadata = { - metadataBase: new URL(env.NEXT_PUBLIC_APP_URL), title: 'Forms', description: 'Manage your form endpoints', }; diff --git a/apps/web/src/app/(main)/dashboard/settings/api-keys/page.tsx b/apps/web/src/app/(main)/dashboard/settings/api-keys/page.tsx index 1080c91..980e8e8 100644 --- a/apps/web/src/app/(main)/dashboard/settings/api-keys/page.tsx +++ b/apps/web/src/app/(main)/dashboard/settings/api-keys/page.tsx @@ -3,13 +3,11 @@ import { redirect } from 'next/navigation'; import type { Metadata } from 'next'; import { getSession } from '@formbase/auth/server'; -import { env } from '@formbase/env'; import { Separator } from '@formbase/ui/primitives/separator'; import { ApiKeysSection } from './api-keys-section'; export const metadata: Metadata = { - metadataBase: new URL(env.NEXT_PUBLIC_APP_URL), title: 'API Keys | Formbase', description: 'Manage your API keys for programmatic access', }; diff --git a/apps/web/src/app/(main)/dashboard/settings/page.tsx b/apps/web/src/app/(main)/dashboard/settings/page.tsx index e6ee568..11332ff 100644 --- a/apps/web/src/app/(main)/dashboard/settings/page.tsx +++ b/apps/web/src/app/(main)/dashboard/settings/page.tsx @@ -3,13 +3,11 @@ import { redirect } from 'next/navigation'; import type { Metadata } from 'next'; import { getSession } from '@formbase/auth/server'; -import { env } from '@formbase/env'; import { Separator } from '@formbase/ui/primitives/separator'; import { ProfileForm } from './profile-form'; export const metadata: Metadata = { - metadataBase: new URL(env.NEXT_PUBLIC_APP_URL), title: 'Settings | Formbase', description: 'Manage your account settings', }; diff --git a/apps/web/src/app/api/s/[id]/route.ts b/apps/web/src/app/api/s/[id]/route.ts index ff51507..d518607 100644 --- a/apps/web/src/app/api/s/[id]/route.ts +++ b/apps/web/src/app/api/s/[id]/route.ts @@ -1,7 +1,6 @@ import { userAgent } from 'next/server'; import { type RouterOutputs } from '@formbase/api'; -import { env } from '@formbase/env'; import { sendMail } from '~/lib/email/mailer'; import { renderNewSubmissionEmail } from '~/lib/email/templates/new-submission'; @@ -64,6 +63,7 @@ async function getFormData(request: Request): Promise { async function processFileUploads( formData: Record, formDataFromRequest: FormData, + requestOrigin: string, ) { const fileKeys = Object.keys(formData).filter( (key) => formData[key] instanceof Blob, @@ -71,7 +71,7 @@ async function processFileUploads( for (const key of fileKeys) { const file = formDataFromRequest.get(key) as File; - const fileUrl = await uploadFileFromBlob({ file }); + const fileUrl = await uploadFileFromBlob({ file, origin: requestOrigin }); assignFileOrImage({ formData, key, fileUrl }); } } @@ -103,6 +103,7 @@ export async function POST( const { id } = await params; const formId = id; + const requestOrigin = new URL(request.url).origin; const form = await api.form.getFormById({ formId }); if (!form) { return new Response('Form not found', { @@ -117,7 +118,11 @@ export async function POST( const { data: formData } = formDataResult; if (formDataResult.source === 'formData') { - await processFileUploads(formData, formDataResult.rawFormData); + await processFileUploads( + formData, + formDataResult.rawFormData, + requestOrigin, + ); } const honeypotField = form.honeypotField; @@ -158,7 +163,7 @@ export async function POST( return new Response(null, { status: 303, headers: { - Location: `${env.NEXT_PUBLIC_APP_URL}/s/${formId}`, + Location: new URL(`/s/${formId}`, requestOrigin).toString(), ...CORS_HEADERS, }, }); diff --git a/apps/web/src/app/api/v1/openapi.json/route.ts b/apps/web/src/app/api/v1/openapi.json/route.ts index 2c73c81..fcc4572 100644 --- a/apps/web/src/app/api/v1/openapi.json/route.ts +++ b/apps/web/src/app/api/v1/openapi.json/route.ts @@ -1,15 +1,14 @@ import { generateOpenApiDocument } from 'trpc-to-openapi'; import { apiV1Router } from '@formbase/api/routers/api-v1'; -import { env } from '@formbase/env'; export const dynamic = 'force-dynamic'; -export function GET() { +export function GET(request: Request) { const openApiDocument = generateOpenApiDocument(apiV1Router, { title: 'Formbase API', version: '1.0.0', - baseUrl: `${env.NEXT_PUBLIC_APP_URL}/api/v1`, + baseUrl: `${new URL(request.url).origin}/api/v1`, description: 'Public REST API for managing forms and submissions.', securitySchemes: { bearerAuth: { diff --git a/apps/web/src/app/robots.ts b/apps/web/src/app/robots.ts index 4e581a1..9916485 100644 --- a/apps/web/src/app/robots.ts +++ b/apps/web/src/app/robots.ts @@ -1,13 +1,28 @@ import { type MetadataRoute } from 'next'; +import { headers } from 'next/headers'; import { absoluteUrl } from '@formbase/utils/server'; -export default function robots(): MetadataRoute.Robots { +export default async function robots(): Promise { + const headersList = await headers(); + const host = headersList.get('x-forwarded-host') ?? headersList.get('host'); + + if (!host) { + throw new Error('Host header is required to build robots URLs'); + } + + const protocol = + headersList.get('x-forwarded-proto') ?? + (host.startsWith('localhost') || host.startsWith('127.') + ? 'http' + : 'https'); + const origin = `${protocol}://${host}`; + return { rules: { userAgent: '*', allow: '/', }, - sitemap: absoluteUrl('/sitemap.xml'), + sitemap: absoluteUrl('/sitemap.xml', origin), }; } diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index a91fee0..e34ee2a 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -1,11 +1,24 @@ import { type MetadataRoute } from 'next'; +import { headers } from 'next/headers'; import { absoluteUrl } from '@formbase/utils/server'; -// eslint-disable-next-line @typescript-eslint/require-await export default async function sitemap(): Promise { + const headersList = await headers(); + const host = headersList.get('x-forwarded-host') ?? headersList.get('host'); + + if (!host) { + throw new Error('Host header is required to build sitemap URLs'); + } + + const protocol = + headersList.get('x-forwarded-proto') ?? + (host.startsWith('localhost') || host.startsWith('127.') + ? 'http' + : 'https'); + const origin = `${protocol}://${host}`; const routes = ['/', '/dashboard'].map((route) => ({ - url: absoluteUrl(route), + url: absoluteUrl(route, origin), lastModified: new Date().toISOString(), })); diff --git a/apps/web/src/lib/upload-file.ts b/apps/web/src/lib/upload-file.ts index b34788a..43e4e43 100644 --- a/apps/web/src/lib/upload-file.ts +++ b/apps/web/src/lib/upload-file.ts @@ -39,9 +39,15 @@ const toHex = (buffer: ArrayBuffer) => .join(''); async function hmac(key: ArrayBuffer | Uint8Array | string, value: string) { + const keyData = + typeof key === 'string' + ? encoder.encode(key) + : key instanceof Uint8Array + ? new Uint8Array(key).buffer + : key; const cryptoKey = await crypto.subtle.importKey( 'raw', - typeof key === 'string' ? encoder.encode(key) : key, + keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'], @@ -162,8 +168,10 @@ const createStorageUrl = (config: StorageConfig, key?: string) => { ); }; -const createFileUrl = (key: string) => - new URL(`/api/files/${encodePath(key)}`, env.NEXT_PUBLIC_APP_URL).toString(); +const createFileUrl = (key: string, origin?: string) => { + const path = `/api/files/${encodePath(key)}`; + return origin ? new URL(path, origin).toString() : path; +}; async function createPresignedUrl({ config, @@ -244,8 +252,17 @@ async function sendStorageRequest({ key?: string; method: 'PUT'; }) { - const url = await createPresignedUrl({ config, headers, key, method }); - return fetch(url, { body, headers, method }); + const url = await createPresignedUrl({ + config, + method, + ...(headers ? { headers } : {}), + ...(key ? { key } : {}), + }); + return fetch(url, { + method, + ...(body === undefined ? {} : { body }), + ...(headers ? { headers } : {}), + }); } const getFileExtension = (mimetype: string) => { @@ -253,7 +270,11 @@ const getFileExtension = (mimetype: string) => { return subtype?.split('+').at(0) ?? 'bin'; }; -export async function uploadFile(file: BodyInit, mimetype: string) { +export async function uploadFile( + file: BodyInit, + mimetype: string, + origin?: string, +) { const config = getStorageConfig(); const name = `${generateId(15)}.${getFileExtension(mimetype)}`; const contentType = mimetype || 'application/octet-stream'; @@ -272,15 +293,17 @@ export async function uploadFile(file: BodyInit, mimetype: string) { throw new Error(`Storage upload failed with status ${response.status}`); } - return createFileUrl(name); + return createFileUrl(name, origin); } export async function uploadFileFromBlob({ file, + origin, }: { file: Blob; + origin?: string; }): Promise { - return uploadFile(file, file.type); + return uploadFile(file, file.type, origin); } export function assignFileOrImage({ diff --git a/apps/web/wrangler.jsonc b/apps/web/wrangler.jsonc index 49ddc6c..d0649cc 100644 --- a/apps/web/wrangler.jsonc +++ b/apps/web/wrangler.jsonc @@ -2,7 +2,7 @@ "$schema": "node_modules/wrangler/config-schema.json", "name": "formbase-web", "main": ".open-next/worker.js", - "workers_dev": false, + "workers_dev": true, "preview_urls": false, "compatibility_date": "2026-05-01", "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], diff --git a/packages/auth/client.ts b/packages/auth/client.ts index 16f09d5..ea35aff 100644 --- a/packages/auth/client.ts +++ b/packages/auth/client.ts @@ -1,7 +1,5 @@ import { createAuthClient } from 'better-auth/react'; -export const authClient = createAuthClient({ - baseURL: process.env['NEXT_PUBLIC_APP_URL'], -}); +export const authClient = createAuthClient(); export const { signIn, signUp, signOut, useSession } = authClient; diff --git a/packages/auth/index.ts b/packages/auth/index.ts index e63bbc7..5fa108f 100644 --- a/packages/auth/index.ts +++ b/packages/auth/index.ts @@ -7,63 +7,79 @@ import { env } from '@formbase/env'; import { sendResetPasswordEmail, sendVerificationEmail } from './email'; -const socialProviders = { - ...(env.AUTH_GITHUB_ID && env.AUTH_GITHUB_SECRET - ? { - github: { - clientId: env.AUTH_GITHUB_ID, - clientSecret: env.AUTH_GITHUB_SECRET, - }, - } - : {}), - ...(env.AUTH_GOOGLE_ID && env.AUTH_GOOGLE_SECRET - ? { - google: { - clientId: env.AUTH_GOOGLE_ID, - clientSecret: env.AUTH_GOOGLE_SECRET, - }, - } - : {}), -}; +const createAuth = () => { + const socialProviders = { + ...(env.AUTH_GITHUB_ID && env.AUTH_GITHUB_SECRET + ? { + github: { + clientId: env.AUTH_GITHUB_ID, + clientSecret: env.AUTH_GITHUB_SECRET, + }, + } + : {}), + ...(env.AUTH_GOOGLE_ID && env.AUTH_GOOGLE_SECRET + ? { + google: { + clientId: env.AUTH_GOOGLE_ID, + clientSecret: env.AUTH_GOOGLE_SECRET, + }, + } + : {}), + }; -export const auth = betterAuth({ - baseURL: env.NEXT_PUBLIC_APP_URL, - database: drizzleAdapter(db, { - provider: 'sqlite', - schema: { - ...schema, - user: schema.users, - session: schema.sessions, - account: schema.accounts, - verification: schema.verifications, + return betterAuth({ + database: drizzleAdapter(db, { + provider: 'sqlite', + schema: { + ...schema, + user: schema.users, + session: schema.sessions, + account: schema.accounts, + verification: schema.verifications, + }, + }), + emailAndPassword: { + enabled: true, + disableSignUp: env.ALLOW_SIGNIN_SIGNUP === 'false', + sendResetPassword: async ({ user, url }) => { + await sendResetPasswordEmail({ + email: user.email, + url, + }); + }, }, - }), - emailAndPassword: { - enabled: true, - disableSignUp: env.ALLOW_SIGNIN_SIGNUP === 'false', - sendResetPassword: async ({ user, token }) => { - const resetUrl = `${env.NEXT_PUBLIC_APP_URL}/reset-password/${token}`; - await sendResetPasswordEmail({ - email: user.email, - url: resetUrl, - }); + emailVerification: { + sendOnSignUp: !env.SKIP_EMAIL_VERIFICATION, + autoSignInAfterVerification: true, + sendVerificationEmail: async ({ user, url }) => { + if (env.SKIP_EMAIL_VERIFICATION) return; + await sendVerificationEmail({ + email: user.email, + url, + }); + }, }, - }, - emailVerification: { - sendOnSignUp: !env.SKIP_EMAIL_VERIFICATION, - autoSignInAfterVerification: true, - sendVerificationEmail: async ({ user, token }) => { - if (env.SKIP_EMAIL_VERIFICATION) return; - const verifyUrl = `${env.NEXT_PUBLIC_APP_URL}/verify-email?token=${token}`; - await sendVerificationEmail({ - email: user.email, - url: verifyUrl, - }); + ...(Object.keys(socialProviders).length > 0 ? { socialProviders } : {}), + session: { + cookieCache: { enabled: true, maxAge: 60 * 5 }, }, - }, - ...(Object.keys(socialProviders).length > 0 ? { socialProviders } : {}), - session: { - cookieCache: { enabled: true, maxAge: 60 * 5 }, + }); +}; + +type Auth = ReturnType; + +let authInstance: Auth | undefined; + +const getAuth = () => { + authInstance ??= createAuth(); + return authInstance; +}; + +export const auth = new Proxy({} as Auth, { + get(_target, property) { + const authClient = getAuth(); + const value = Reflect.get(authClient, property); + return typeof value === 'function' ? value.bind(authClient) : value; }, }); diff --git a/packages/auth/server.ts b/packages/auth/server.ts index ce3224a..416d412 100644 --- a/packages/auth/server.ts +++ b/packages/auth/server.ts @@ -9,7 +9,8 @@ const cache = : unknown>(fn: T) => fn; export const getSession = cache(async () => { - return auth.api.getSession({ headers: await headers() }); + const requestHeaders = await headers(); + return auth.api.getSession({ headers: requestHeaders }); }); export async function requireAuth() { diff --git a/packages/db/credentials.ts b/packages/db/credentials.ts index 2c9c0fa..1bbc89b 100644 --- a/packages/db/credentials.ts +++ b/packages/db/credentials.ts @@ -7,6 +7,9 @@ type DatabaseCredentials = { export const getDatabaseCredentials = (): DatabaseCredentials => { const databaseUrl = env.DATABASE_URL; + if (!databaseUrl) { + throw new Error('DATABASE_URL is required'); + } if (!databaseUrl.startsWith('libsql://')) { return { url: databaseUrl }; diff --git a/packages/db/index.ts b/packages/db/index.ts index a721003..af304f0 100644 --- a/packages/db/index.ts +++ b/packages/db/index.ts @@ -1,3 +1,6 @@ +import type { Client } from '@libsql/client'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; + import { createClient } from '@libsql/client'; import { and, @@ -17,10 +20,47 @@ import { drizzle } from 'drizzle-orm/libsql'; import { getDatabaseCredentials } from './credentials'; import * as schema from './schema'; -export const queryClient = createClient(getDatabaseCredentials()); +let queryClientInstance: Client | undefined; + +const getQueryClient = () => { + queryClientInstance ??= createClient(getDatabaseCredentials()); + return queryClientInstance; +}; + +export const queryClient = new Proxy({} as Client, { + get(_target, property) { + const client = getQueryClient(); + const value = Reflect.get(client, property); + return typeof value === 'function' ? value.bind(client) : value; + }, + set(_target, property, value) { + return Reflect.set(getQueryClient(), property, value); + }, +}); + +const createDb = () => + drizzle(queryClient, { + schema: schema, + }); + +type Database = LibSQLDatabase; + +let dbInstance: Database | undefined; + +const getDb = () => { + dbInstance ??= createDb(); + return dbInstance; +}; -export const db = drizzle(queryClient, { - schema: schema, +export const db = new Proxy({} as Database, { + get(_target, property) { + const database = getDb(); + const value = Reflect.get(database, property); + return typeof value === 'function' ? value.bind(database) : value; + }, + set(_target, property, value) { + return Reflect.set(getDb(), property, value); + }, }); export const drizzlePrimitives = { diff --git a/packages/env/index.ts b/packages/env/index.ts index f0d34fc..e394370 100644 --- a/packages/env/index.ts +++ b/packages/env/index.ts @@ -13,7 +13,6 @@ const booleanFromString = z.preprocess((value) => { export const env = createEnv({ shared: { NODE_ENV: z.enum(['development', 'test', 'production']).optional(), - NEXT_PUBLIC_APP_URL: z.string().url(), }, server: { PORT: z.coerce.number().default(3000), diff --git a/packages/utils/server.ts b/packages/utils/server.ts index 207c969..bdbfc4b 100644 --- a/packages/utils/server.ts +++ b/packages/utils/server.ts @@ -1,5 +1,3 @@ -import { env } from '@formbase/env'; - -export function absoluteUrl(path: string) { - return `${env.NEXT_PUBLIC_APP_URL}${path}`; +export function absoluteUrl(path: string, origin: string) { + return new URL(path, origin).toString(); } diff --git a/packages/utils/url.ts b/packages/utils/url.ts index ebff741..c5532d6 100644 --- a/packages/utils/url.ts +++ b/packages/utils/url.ts @@ -1,8 +1,5 @@ export function getBaseUrl() { if (typeof window !== 'undefined') return window.location.origin; - if (process.env['NEXT_PUBLIC_APP_URL']) { - return process.env['NEXT_PUBLIC_APP_URL']; - } if (process.env['VERCEL_URL']) return `https://${process.env['VERCEL_URL']}`; return `http://localhost:${process.env['PORT'] ?? 3000}`; } diff --git a/tests/routes/upload-file.test.ts b/tests/routes/upload-file.test.ts index 1b53694..bc014af 100644 --- a/tests/routes/upload-file.test.ts +++ b/tests/routes/upload-file.test.ts @@ -81,9 +81,7 @@ describe('upload-file storage signing', () => { const { uploadFile } = await import('~/lib/upload-file'); const fileUrl = await uploadFile(new Blob(['']), 'image/svg+xml'); - expect(fileUrl).toMatch( - /^http:\/\/localhost:3000\/api\/files\/[0-9A-Za-z]{15}\.svg$/, - ); + expect(fileUrl).toMatch(/^\/api\/files\/[0-9A-Za-z]{15}\.svg$/); const [url, init] = fetch.mock.calls[0] as [string, RequestInit]; @@ -92,4 +90,20 @@ describe('upload-file storage signing', () => { ); expect(init.headers).toEqual({ 'Content-Type': 'image/svg+xml' }); }); + + it('creates absolute file URLs from a request origin', async () => { + const fetch = vi.fn(async () => new Response(null, { status: 200 })); + vi.stubGlobal('fetch', fetch); + + const { uploadFile } = await import('~/lib/upload-file'); + const fileUrl = await uploadFile( + new Blob(['']), + 'image/svg+xml', + 'https://forms.example', + ); + + expect(fileUrl).toMatch( + /^https:\/\/forms\.example\/api\/files\/[0-9A-Za-z]{15}\.svg$/, + ); + }); }); diff --git a/tests/vitest.config.ts b/tests/vitest.config.ts index 296ae6c..3abcfc2 100644 --- a/tests/vitest.config.ts +++ b/tests/vitest.config.ts @@ -1,11 +1,11 @@ import path from 'path'; + import { defineConfig } from 'vitest/config'; process.env['SKIP_ENV_VALIDATION'] = 'true'; process.env.NODE_ENV = 'test'; process.env['DATABASE_URL'] = 'file::memory:?cache=shared'; process.env['BETTER_AUTH_SECRET'] = 'test-secret-minimum-32-characters-long-for-testing'; -process.env['NEXT_PUBLIC_APP_URL'] = 'http://localhost:3000'; process.env['ALLOW_SIGNIN_SIGNUP'] = 'true'; export default defineConfig({ diff --git a/tests/vitest.setup.ts b/tests/vitest.setup.ts index ba27345..176489f 100644 --- a/tests/vitest.setup.ts +++ b/tests/vitest.setup.ts @@ -11,7 +11,6 @@ process.env.NODE_ENV = 'test'; process.env['DATABASE_URL'] = 'file::memory:?cache=shared'; process.env['BETTER_AUTH_SECRET'] = 'test-secret-minimum-32-characters-long-for-testing'; -process.env['NEXT_PUBLIC_APP_URL'] = 'http://localhost:3000'; process.env['ALLOW_SIGNIN_SIGNUP'] = 'true'; beforeAll(async () => {