-
Notifications
You must be signed in to change notification settings - Fork 29k
feat: add validation for root-level files when src directory exists #82514
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: canary
Are you sure you want to change the base?
Changes from 1 commit
ef4bcb1
57e6e21
8bafaef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Add SrcDirectoryError class for consistent error messaging - Add validateMiddlewareInSrcDir and validateInstrumentationInSrcDir functions - Integrate validation in both dev bundler and build processes - Throw error when middleware/instrumentation files are outside src/ when src/ directory exists - Add test cases to verify validation behavior Co-Authored-By: Jiwon Choi <devjiwonchoi@gmail.com>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,6 +93,11 @@ import { | |
TurborepoAccessTraceResult, | ||
writeTurborepoAccessTraceResult, | ||
} from './turborepo-access-trace' | ||
import { | ||
validateMiddlewareInSrcDir, | ||
validateInstrumentationInSrcDir, | ||
} from './utils' | ||
|
||
|
||
import { | ||
eventBuildOptimize, | ||
|
@@ -1157,6 +1162,17 @@ export default async function build( | |
|
||
NextBuildContext.hasInstrumentationHook = hasInstrumentationHook | ||
|
||
if (isSrcDir) { | ||
for (const rootPath of rootPaths) { | ||
if (rootPath.includes(MIDDLEWARE_FILENAME)) { | ||
validateMiddlewareInSrcDir(rootPath, isSrcDir) | ||
} | ||
if (rootPath.includes(INSTRUMENTATION_HOOK_FILENAME)) { | ||
validateInstrumentationInSrcDir(rootPath, isSrcDir) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The src directory validation for middleware and instrumentation files never triggers during builds because the validation functions expect paths without file extensions, but receive paths with extensions. View DetailsAnalysisThe build process creates const rootPaths = Array.from(await getFilesInDir(rootDir))
.filter((file) => includes.some((include) => include.test(file)))
.map((file) => path.join(rootDir, file).replace(dir, '')) However, the validation functions export function isMiddlewareFile(file: string) {
return (
file === `/${MIDDLEWARE_FILENAME}` || file === `/src/${MIDDLEWARE_FILENAME}`
)
} This means This bug means the new validation feature is completely non-functional during builds, potentially allowing users to incorrectly place middleware/instrumentation files outside the src directory when they should be required to be inside it. RecommendationStrip the file extension from the path before calling the validation functions. Modify the validation calls in lines 1167-1172: if (rootPath.includes(MIDDLEWARE_FILENAME)) {
const pathWithoutExtension = rootPath.replace(/\.[^/.]+$/, '')
validateMiddlewareInSrcDir(pathWithoutExtension, isSrcDir)
}
if (rootPath.includes(INSTRUMENTATION_HOOK_FILENAME)) {
const pathWithoutExtension = rootPath.replace(/\.[^/.]+$/, '')
validateInstrumentationInSrcDir(pathWithoutExtension, isSrcDir)
} Alternatively, update the validation functions to handle paths with extensions by modifying |
||
} | ||
} | ||
|
||
const previewProps: __ApiPreviewProps = await generatePreviewKeys({ | ||
isBuild: true, | ||
distDir, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1821,6 +1821,33 @@ export class NestedMiddlewareError extends Error { | |
} | ||
} | ||
|
||
export class SrcDirectoryError extends Error { | ||
constructor( | ||
fileName: string, | ||
fileType: 'middleware' | 'instrumentation' | ||
) { | ||
super( | ||
`${fileType === 'middleware' ? 'Middleware' : 'Instrumentation'} file found outside src directory.\n` + | ||
`Found: ${fileName}\n` + | ||
`When using the src directory, ${fileType} files must be placed inside the src directory.\n` + | ||
`Please move your ${fileType} file to src/${fileType}.\n` + | ||
`Read More - https://nextjs.org/docs/app/api-reference/file-conventions/src-folder` | ||
) | ||
} | ||
} | ||
|
||
export function validateMiddlewareInSrcDir(fileName: string, isSrcDir: boolean) { | ||
if (isSrcDir && isMiddlewareFile(fileName) && !fileName.startsWith('/src/')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new validation functions use hardcoded View DetailsAnalysisThe On Windows systems, this creates paths like
This is inconsistent with the dev server code path, which uses The codebase has established patterns for handling cross-platform paths (see RecommendationNormalize the path separators before validation to ensure cross-platform compatibility. Update both validation functions to use forward slashes consistently: export function validateMiddlewareInSrcDir(fileName: string, isSrcDir: boolean) {
const normalizedPath = fileName.replace(/\\/g, '/')
if (isSrcDir && isMiddlewareFile(fileName) && !normalizedPath.startsWith('/src/')) {
throw new SrcDirectoryError(fileName, 'middleware')
}
}
export function validateInstrumentationInSrcDir(fileName: string, isSrcDir: boolean) {
const normalizedPath = fileName.replace(/\\/g, '/')
if (isSrcDir && isInstrumentationHookFile(fileName) && !normalizedPath.startsWith('/src/')) {
throw new SrcDirectoryError(fileName, 'instrumentation')
}
} Alternatively, import and use the existing |
||
throw new SrcDirectoryError(fileName, 'middleware') | ||
} | ||
} | ||
|
||
export function validateInstrumentationInSrcDir(fileName: string, isSrcDir: boolean) { | ||
if (isSrcDir && isInstrumentationHookFile(fileName) && !fileName.startsWith('/src/')) { | ||
throw new SrcDirectoryError(fileName, 'instrumentation') | ||
} | ||
} | ||
|
||
export function getSupportedBrowsers( | ||
dir: string, | ||
isDevelopment: boolean | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module.exports = { | ||
experimental: { | ||
appDir: false, | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function Home() { | ||
return <div>Hello World</div> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* eslint-env jest */ | ||
|
||
import fs from 'fs-extra' | ||
import { join } from 'path' | ||
import { nextBuild, nextStart, findPort, killApp } from 'next-test-utils' | ||
|
||
const appDir = join(__dirname, '../') | ||
|
||
describe('Src Directory Validation', () => { | ||
beforeEach(async () => { | ||
await fs.remove(join(appDir, 'middleware.js')).catch(() => {}) | ||
await fs.remove(join(appDir, 'instrumentation.js')).catch(() => {}) | ||
}) | ||
|
||
afterEach(async () => { | ||
await fs.remove(join(appDir, 'middleware.js')).catch(() => {}) | ||
await fs.remove(join(appDir, 'instrumentation.js')).catch(() => {}) | ||
}) | ||
|
||
it('should throw error when middleware is outside src directory', async () => { | ||
await fs.writeFile( | ||
join(appDir, 'middleware.js'), | ||
`export function middleware() { return new Response('middleware') }` | ||
) | ||
|
||
const result = await nextBuild(appDir, [], { stderr: true, stdout: true }) | ||
expect(result.stderr + result.stdout).toContain('Middleware file found outside src directory') | ||
}) | ||
|
||
it('should throw error when instrumentation is outside src directory', async () => { | ||
await fs.writeFile( | ||
join(appDir, 'instrumentation.js'), | ||
`export function register() { console.log('instrumentation') }` | ||
) | ||
|
||
const result = await nextBuild(appDir, [], { stderr: true, stdout: true }) | ||
expect(result.stderr + result.stdout).toContain('Instrumentation file found outside src directory') | ||
}) | ||
|
||
it('should work correctly when middleware is inside src directory', async () => { | ||
await fs.writeFile( | ||
join(appDir, 'src/middleware.js'), | ||
`export function middleware() { return new Response('middleware') }` | ||
) | ||
|
||
const result = await nextBuild(appDir, [], { stderr: true, stdout: true }) | ||
expect(result.code).toBe(0) | ||
}) | ||
|
||
it('should work correctly when instrumentation is inside src directory', async () => { | ||
await fs.writeFile( | ||
join(appDir, 'src/instrumentation.js'), | ||
`export function register() { console.log('instrumentation') }` | ||
) | ||
|
||
const result = await nextBuild(appDir, [], { stderr: true, stdout: true }) | ||
expect(result.code).toBe(0) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The build process fails to detect middleware and instrumentation files placed at the project root when using src directory structure, preventing helpful validation errors from being shown to users.
View Details
Analysis
When using a src directory structure (
src/pages
orsrc/app
), the build process should detect if middleware or instrumentation files are incorrectly placed at the project root instead of inside thesrc/
directory, and throw a clear error to guide users.However, there's a bug in the file detection logic on line 1144. The code calculates:
When
pagesDir
is/project/src/pages
, this results inrootDir = /project/src
. The subsequentgetFilesInDir(rootDir)
call only searches within the src directory, completely missing files that are incorrectly placed at the actual project root (/project/
).This means users who place
middleware.js
orinstrumentation.js
at the project root while using src directory structure won't receive the helpful build-time validation error. Instead, they'll experience confusing runtime behavior without clear guidance on where to place their files.The test case "should throw error when middleware is outside src directory" expects this validation to work, confirming this is a bug rather than intended behavior.
Recommendation
Change the rootDir calculation to properly search the project root directory. Replace line 1144 with:
Or alternatively, search both locations when
isSrcDir
is true:This ensures that when using src directory structure, the validation checks both the project root (to catch incorrectly placed files) and the src directory (to allow correctly placed files).