From 7ced6c08188455ab26fe4629d084d40b0d0f69ca Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 28 May 2026 11:29:54 +0000 Subject: [PATCH] Move inviteRequestSchema out of route.ts to satisfy Next 15 build Next.js 15 enforces a strict allowlist of named exports from route.ts files; "inviteRequestSchema" was rejected by the route-validator with "is not a valid Route export field" during npm run build. Splitting the schema into a sibling module (which is exempt from the allowlist) and importing it from route.ts and the test. Renames the test file to match its target module. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../{route.test.ts => inviteRequestSchema.test.ts} | 2 +- .../[portfolioId]/collaborators/inviteRequestSchema.ts | 10 ++++++++++ .../api/portfolio/[portfolioId]/collaborators/route.ts | 8 +------- 3 files changed, 12 insertions(+), 8 deletions(-) rename src/app/api/portfolio/[portfolioId]/collaborators/{route.test.ts => inviteRequestSchema.test.ts} (94%) create mode 100644 src/app/api/portfolio/[portfolioId]/collaborators/inviteRequestSchema.ts diff --git a/src/app/api/portfolio/[portfolioId]/collaborators/route.test.ts b/src/app/api/portfolio/[portfolioId]/collaborators/inviteRequestSchema.test.ts similarity index 94% rename from src/app/api/portfolio/[portfolioId]/collaborators/route.test.ts rename to src/app/api/portfolio/[portfolioId]/collaborators/inviteRequestSchema.test.ts index 7b855f3f..e332c555 100644 --- a/src/app/api/portfolio/[portfolioId]/collaborators/route.test.ts +++ b/src/app/api/portfolio/[portfolioId]/collaborators/inviteRequestSchema.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { inviteRequestSchema } from "./route"; +import { inviteRequestSchema } from "./inviteRequestSchema"; describe("inviteRequestSchema", () => { it("accepts an invite request with just email and role (no name)", () => { diff --git a/src/app/api/portfolio/[portfolioId]/collaborators/inviteRequestSchema.ts b/src/app/api/portfolio/[portfolioId]/collaborators/inviteRequestSchema.ts new file mode 100644 index 00000000..12526c67 --- /dev/null +++ b/src/app/api/portfolio/[portfolioId]/collaborators/inviteRequestSchema.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; +import { ROLE_OPTIONS } from "@/app/portfolio/[slug]/(portfolio)/settings/roles"; + +// Body schema for POST /api/portfolio/[portfolioId]/collaborators. Lives in +// its own file because Next.js 15 rejects non-handler named exports from +// route.ts. See route.test.ts for the contract tests. +export const inviteRequestSchema = z.object({ + email: z.string().email(), + role: z.enum(ROLE_OPTIONS), +}); diff --git a/src/app/api/portfolio/[portfolioId]/collaborators/route.ts b/src/app/api/portfolio/[portfolioId]/collaborators/route.ts index 8fa8ca8d..4db4432b 100644 --- a/src/app/api/portfolio/[portfolioId]/collaborators/route.ts +++ b/src/app/api/portfolio/[portfolioId]/collaborators/route.ts @@ -29,6 +29,7 @@ import { denyIfNotAdmin, resolvePortfolioPrivilege, } from "@/app/lib/resolvePortfolioPrivilege"; +import { inviteRequestSchema } from "./inviteRequestSchema"; // Get collaborators (users) that have access to the portfolio, plus the // effective privilege of the requesting user (so the UI knows which actions @@ -192,13 +193,6 @@ export async function PUT( } } -// Body schema for the invite endpoint. Exported so the contract can be -// unit-tested directly — see route.test.ts. -export const inviteRequestSchema = z.object({ - email: z.string().email(), - role: z.enum(ROLE_OPTIONS), -}); - // POST: invite a user by email. // // Unified flow: in nearly every case we write a pending portfolio_invitations