From e9c1d3ed470ae8b67a7c3ef9cd58953fb42ab6dd Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 15 Mar 2026 11:19:34 -0700 Subject: [PATCH] fix: detect Next.js route groups as valid project structure The project validator failed to recognize Next.js projects using route groups (parenthesized directories like (app), (dashboard)). The isTargetFile function did an exact directory match against potential paths, missing layout files inside route group subdirectories. Added route group pattern matching to isTargetFile so that paths like app/(marketing)/layout.tsx match the potentialPath 'app'. Fixes #2936 --- packages/utility/src/path.ts | 10 +++++++++- packages/utility/test/path.test.ts | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/utility/src/path.ts b/packages/utility/src/path.ts index 5fa8fff45d..cbe9f72ed4 100644 --- a/packages/utility/src/path.ts +++ b/packages/utility/src/path.ts @@ -84,7 +84,15 @@ export const isTargetFile = ( } const dirName = normalize(path.dirname(targetFile)); - return potentialPaths.some((p) => normalize(p) === dirName); + return potentialPaths.some((p) => { + const normalizedP = normalize(p); + if (normalizedP === dirName) return true; + // Support Next.js route groups: match files inside (group) subdirectories + // e.g., app/(marketing)/layout.tsx should match potentialPath 'app' + const parentDir = normalize(path.dirname(dirName)); + const groupDir = path.basename(dirName); + return parentDir === normalizedP && /^\([^)]+\)$/.test(groupDir); + }); }; export const isRootLayoutFile = ( diff --git a/packages/utility/test/path.test.ts b/packages/utility/test/path.test.ts index 91a5fa5d07..bbbdd0b7ae 100644 --- a/packages/utility/test/path.test.ts +++ b/packages/utility/test/path.test.ts @@ -190,4 +190,24 @@ describe('isTargetFile', () => { const targetFile = 'src/components/layout.tsx'; expect(isRootLayoutFile(targetFile)).toBe(false); }); + + test('returns true for layout in a Next.js route group under app/', () => { + expect(isRootLayoutFile('app/(marketing)/layout.tsx')).toBe(true); + }); + + test('returns true for layout in a Next.js route group under src/app/', () => { + expect(isRootLayoutFile('src/app/(dashboard)/layout.tsx')).toBe(true); + }); + + test('returns true for layout in route group with jsx extension', () => { + expect(isRootLayoutFile('app/(app)/layout.jsx')).toBe(true); + }); + + test('returns false for layout nested deeper than one route group level', () => { + expect(isRootLayoutFile('app/(app)/nested/layout.tsx')).toBe(false); + }); + + test('returns false for layout in directory that looks like route group but is not', () => { + expect(isRootLayoutFile('app/marketing/layout.tsx')).toBe(false); + }); });