mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
Restart the next dev server if .next is deleted (#92135)
This PR is AI generated. If this directory is deleted while the dev server is running, we can't reasonably recover, so we should restart the process. Addresses https://vercel.slack.com/archives/C03KAR5DCKC/p1774757692640999
This commit is contained in:
@@ -524,25 +524,40 @@ export async function startServer(
|
||||
server.listen(port, hostname)
|
||||
})
|
||||
|
||||
// Watch config files for changes and distDir ancestors for deletion.
|
||||
if (isDev) {
|
||||
function watchConfigFiles(
|
||||
dirToWatch: string,
|
||||
onChange: (filename: string) => void
|
||||
) {
|
||||
const wp = new Watchpack()
|
||||
wp.watch({
|
||||
files: CONFIG_FILES.map((file) => path.join(dirToWatch, file)),
|
||||
})
|
||||
wp.on('change', onChange)
|
||||
// Note: dir is absolute and normalized (`..` segments removed), `absDistDir`
|
||||
// is also normalized because `path.join()` performs normalization. `distDir`
|
||||
// does not have to be inside of `dir`!
|
||||
const absDistDir = path.join(dir, distDir)
|
||||
// always watch dir and absDistDir
|
||||
const dirWatchPaths: string[] = [dir, absDistDir]
|
||||
// also watch ancestors of absDistDir that are inside of dir.
|
||||
let prevAncestor = absDistDir
|
||||
while (true) {
|
||||
const nextAncestor = path.dirname(prevAncestor)
|
||||
// note: `dirname('/') === '/'` if we happen to reach the FS root
|
||||
if (
|
||||
!nextAncestor.startsWith(dir + path.sep) ||
|
||||
nextAncestor === prevAncestor
|
||||
) {
|
||||
break
|
||||
}
|
||||
dirWatchPaths.push(nextAncestor)
|
||||
prevAncestor = nextAncestor
|
||||
}
|
||||
watchConfigFiles(dir, async (filename) => {
|
||||
if (process.env.__NEXT_DISABLE_MEMORY_WATCHER) {
|
||||
Log.info(
|
||||
`Detected change, manual restart required due to '__NEXT_DISABLE_MEMORY_WATCHER' usage`
|
||||
)
|
||||
|
||||
const configFiles = CONFIG_FILES.map((file) => path.join(dir, file))
|
||||
|
||||
const wp = new Watchpack()
|
||||
wp.watch({
|
||||
files: configFiles,
|
||||
missing: dirWatchPaths,
|
||||
})
|
||||
wp.on('change', async (filename) => {
|
||||
if (!configFiles.includes(filename)) {
|
||||
return
|
||||
}
|
||||
|
||||
Log.warn(
|
||||
`Found a change in ${path.basename(
|
||||
filename
|
||||
@@ -550,6 +565,16 @@ export async function startServer(
|
||||
)
|
||||
process.exit(RESTART_EXIT_CODE)
|
||||
})
|
||||
wp.on('remove', (removedPath: string) => {
|
||||
if (dirWatchPaths.includes(removedPath)) {
|
||||
Log.error(
|
||||
`The directory at "${removedPath}" was deleted.\n\n` +
|
||||
'Deleting this directory while Next.js is running can lead to ' +
|
||||
'undefined behavior. Restarting the server to recover...'
|
||||
)
|
||||
process.exit(RESTART_EXIT_CODE)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return { distDir }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return <p>hello world</p>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,46 @@
|
||||
import { nextTestSetup } from 'e2e-utils'
|
||||
import { retry } from 'next-test-utils'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
describe('app-dir watch-distdir-deletion', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: path.join(__dirname, 'fixture'),
|
||||
})
|
||||
|
||||
it('should restart the server when distDir is deleted', async () => {
|
||||
// Wait for the dev server to be ready
|
||||
await retry(async () => {
|
||||
expect(next.cliOutput).toMatch(/ready/i)
|
||||
})
|
||||
|
||||
// Make a request first to ensure the dev server has compiled
|
||||
const warmupRes = await next.fetch('/')
|
||||
expect(warmupRes.status).toBe(200)
|
||||
|
||||
// Delete .next (which also removes .next/dev, the watched distDir)
|
||||
await fs.promises.rm(path.join(next.testDir, '.next'), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
})
|
||||
|
||||
// Wait for restart message and server to come back up
|
||||
await retry(
|
||||
async () => {
|
||||
expect(next.cliOutput).toMatch(/The directory at .* was deleted/)
|
||||
},
|
||||
30_000,
|
||||
1000
|
||||
)
|
||||
|
||||
// Server should come back up and serve pages
|
||||
await retry(
|
||||
async () => {
|
||||
const res = await next.fetch('/')
|
||||
expect(res.status).toBe(200)
|
||||
},
|
||||
30_000,
|
||||
1000
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user