rsnext/packages/next/server/lib/find-page-file.ts
Hannes Bornö fdc07c0012
App files ending with page registred as page files (#42996)
When building (`next/build/index.ts`) files ending with `page.js` got
picked up by the RegEx which meant files like `not-a-page.js` ended up
as a page. This change makes sure the segment actually starts with
`page`.

Dev had the same issue and also missed taking `pageExtensions` into
account.

Fixes #42972

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm build && pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-11-16 13:46:04 -08:00

72 lines
2.4 KiB
TypeScript

import { fileExists } from '../../lib/file-exists'
import { getPagePaths } from '../../shared/lib/page-path/get-page-paths'
import { nonNullable } from '../../lib/non-nullable'
import { join, sep, normalize } from 'path'
import { promises } from 'fs'
import { warn } from '../../build/output/log'
import chalk from '../../lib/chalk'
async function isTrueCasePagePath(pagePath: string, pagesDir: string) {
const pageSegments = normalize(pagePath).split(sep).filter(Boolean)
const segmentExistsPromises = pageSegments.map(async (segment, i) => {
const segmentParentDir = join(pagesDir, ...pageSegments.slice(0, i))
const parentDirEntries = await promises.readdir(segmentParentDir)
return parentDirEntries.includes(segment)
})
return (await Promise.all(segmentExistsPromises)).every(Boolean)
}
/**
* Finds a page file with the given parameters. If the page is duplicated with
* multiple extensions it will throw, otherwise it will return the *relative*
* path to the page file or null if it is not found.
*
* @param pagesDir Absolute path to the pages folder with trailing `/pages`.
* @param normalizedPagePath The page normalized (it will be denormalized).
* @param pageExtensions Array of page extensions.
*/
export async function findPageFile(
pagesDir: string,
normalizedPagePath: string,
pageExtensions: string[],
isAppDir: boolean
): Promise<string | null> {
const pagePaths = getPagePaths(normalizedPagePath, pageExtensions, isAppDir)
const [existingPath, ...others] = (
await Promise.all(
pagePaths.map(async (path) => {
const filePath = join(pagesDir, path)
return (await fileExists(filePath)) ? path : null
})
)
).filter(nonNullable)
if (!existingPath) {
return null
}
if (!(await isTrueCasePagePath(existingPath, pagesDir))) {
return null
}
if (others.length > 0) {
warn(
`Duplicate page detected. ${chalk.cyan(
join('pages', existingPath)
)} and ${chalk.cyan(
join('pages', others[0])
)} both resolve to ${chalk.cyan(normalizedPagePath)}.`
)
}
return existingPath
}
// Determine if the file is leaf node page file under layouts,
// The filename should start with 'page' and end with one of the allowed extensions
export function isLayoutsLeafPage(filePath: string, pageExtensions: string[]) {
return new RegExp(`(^page|/page)\\.(?:${pageExtensions.join('|')})$`).test(
filePath
)
}