mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
[CLI] Exit non-zero with a clear message when next typegen fails (#94241)
The `typegen` command chained `nextTypegen(...).then(() => process.exit(0))` with no rejection handler. When `next.config` throws during `loadConfig`, the rejection is orphaned by the synchronous `program.parse` and then absorbed by Next's `unhandledRejection` filter, so the process exits `0` having written no route types. A later `tsc` then reports `Cannot find name 'PageProps'`/`'LayoutProps'` (and missing `*.png` modules) with nothing tying it back to typegen. Add a `.catch` that prints an actionable message about the missing route types and `process.exit(1)`, so `next typegen && tsc` halts on failure. Includes an e2e test whose fixture config throws and asserts a non-zero exit with the diagnostic. <!-- NEXT_JS_LLM_PR -->
This commit is contained in:
@@ -518,7 +518,20 @@ program
|
||||
// ensure process exits after typegen completes so open handles/connections
|
||||
// don't cause process to hang
|
||||
import('../cli/next-typegen.js').then((mod) =>
|
||||
mod.nextTypegen(options, directory).then(() => process.exit(0))
|
||||
mod
|
||||
.nextTypegen(options, directory)
|
||||
.then(() => process.exit(0))
|
||||
.catch((err: unknown) => {
|
||||
// Without this, a failed typegen (e.g. `next.config` throwing) is
|
||||
// swallowed as an unhandled rejection that exits 0; surface it with a
|
||||
// non-zero exit so `next typegen && tsc` halts instead of running
|
||||
// against missing route types.
|
||||
console.error(
|
||||
'\n> Unexpected error while generating route types. Original error:\n'
|
||||
)
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
)
|
||||
)
|
||||
.usage('[directory] [options]')
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ReactNode } from 'react'
|
||||
export default function Root({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return <p>hello world</p>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Faithful repro of the real-world trigger: `next typegen` runs with
|
||||
// NODE_ENV=production (see packages/next/src/bin/next.ts), which flips a
|
||||
// `warnOrError` helper to throw when a required env var is missing — exactly the
|
||||
// pattern apps like vercel-site use (e.g. CONTENTFUL_DELIVERY_TOKEN).
|
||||
//
|
||||
// In dev (e.g. during test harness setup) it only warns, so setup is unaffected;
|
||||
// during `next typegen` it throws, taking config loading — and therefore route
|
||||
// type generation — down with it.
|
||||
const isDev = process.env.NODE_ENV !== 'production'
|
||||
const warnOrError = isDev
|
||||
? console.warn
|
||||
: (msg) => {
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
||||
if (!process.env.SOME_REQUIRED_TOKEN) {
|
||||
warnOrError('SOME_REQUIRED_TOKEN is missing from env')
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
const nextConfig = {}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,39 @@
|
||||
import { nextTestSetup } from 'e2e-utils'
|
||||
import { runNextCommand } from 'next-test-utils'
|
||||
|
||||
describe('typegen error diagnostic', () => {
|
||||
const { next, skipped } = nextTestSetup({
|
||||
files: __dirname,
|
||||
skipDeployment: true,
|
||||
// We drive `next typegen` manually; no server needed.
|
||||
skipStart: true,
|
||||
})
|
||||
|
||||
if (skipped) {
|
||||
return
|
||||
}
|
||||
|
||||
it('fails loudly with an actionable message when route types cannot be generated', async () => {
|
||||
// `next typegen` runs with NODE_ENV=production, which makes the fixture's
|
||||
// next.config.js throw on the missing SOME_REQUIRED_TOKEN (the same shape as
|
||||
// a real app's env assertion). typegen therefore cannot generate route types.
|
||||
const { code, stdout, stderr } = await runNextCommand(
|
||||
['typegen', next.testDir],
|
||||
{ stderr: true, stdout: true }
|
||||
)
|
||||
const output = stdout + stderr
|
||||
|
||||
// Regression: `next typegen` used to swallow the failure (exit code 0) and
|
||||
// write no route types, leaving the user with cryptic
|
||||
// `Cannot find name 'PageProps'/'LayoutProps'` errors from a later `tsc` run.
|
||||
// It must instead fail so `next typegen && tsc` chains halt.
|
||||
expect(code).not.toBe(0)
|
||||
|
||||
// The message must connect the failure to route-type generation, so the user
|
||||
// understands why PageProps/LayoutProps go missing.
|
||||
expect(output).toMatch(/route types/i)
|
||||
|
||||
// ...and still surface the underlying cause.
|
||||
expect(output).toContain('SOME_REQUIRED_TOKEN is missing from env')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user