From 74b73620b3f014ed9d4b1106e7eeafead9e263eb Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Wed, 17 Jun 2026 09:35:34 -0300 Subject: [PATCH] feat(build): fail build on misconfigured absolute VITE_API_URL --- config/api-url-contract.ts | 21 +++++++++++++++++++++ tsconfig.node.json | 7 ++++++- vite.config.ts | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 config/api-url-contract.ts diff --git a/config/api-url-contract.ts b/config/api-url-contract.ts new file mode 100644 index 00000000..85f44af6 --- /dev/null +++ b/config/api-url-contract.ts @@ -0,0 +1,21 @@ +/** Absolute VITE_API_URL values must end with this segment (API_PATHS omit /api). */ +export const ABSOLUTE_API_URL_SUFFIX = /\/api\/?$/; + +/** + * Fails fast when an absolute VITE_API_URL omits the /api segment. + * Relative values (e.g. /api) are valid and skipped. + */ +export function assertAbsoluteApiUrlHasApiSegment(apiUrl: string | undefined): void { + const trimmed = apiUrl?.trim(); + if (!trimmed?.startsWith("http")) { + return; + } + + if (!ABSOLUTE_API_URL_SUFFIX.test(trimmed)) { + throw new Error( + `VITE_API_URL absolute URLs must end with /api (got: ${trimmed}). ` + + "API route paths do not include /api — set VITE_API_URL to the full API base, e.g. https://api.example.com/api. " + + "See README.md and .env.example.", + ); + } +} diff --git a/tsconfig.node.json b/tsconfig.node.json index a02b1983..3e43f464 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -16,5 +16,10 @@ "outDir": "./node_modules/.tmp/tsconfig.node", "types": ["node"] }, - "include": ["vite.config.ts", "vitest.config.ts"] + "include": [ + "vite.config.ts", + "vitest.config.ts", + "config/api-url-contract.ts", + "config/api-url-contract.test.ts" + ] } diff --git a/vite.config.ts b/vite.config.ts index c25e540b..204b1ce1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,10 +2,13 @@ import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import path from "node:path"; import { defineConfig, loadEnv, transformWithEsbuild } from "vite"; +import { assertAbsoluteApiUrlHasApiSegment } from "./config/api-url-contract"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ""); + assertAbsoluteApiUrlHasApiSegment(env.VITE_API_URL); + return { plugins: [ {