Derive tsconfig type globs from distDirRoot instead of NODE_ENV - #98945
Open
joshuatownsend wants to merge 1 commit into
Open
joshuatownsend wants to merge 1 commit into
joshuatownsend wants to merge 1 commit into
Conversation
`getTypeDefinitionGlobPatterns` emits both `{distDir}/types` and
`{distDir}/dev/types` so that switching between dev and build does not churn
tsconfig. Which of the two to derive by stripping and which by appending was
decided by `process.env.NODE_ENV === 'development'`.
But `distDir` is given its "/dev" suffix by the build *phase*, in
server/config.ts:
if (phase === PHASE_DEVELOPMENT_SERVER) {
result.distDir = join(result.distDir, 'dev')
}
The two disagree whenever `next dev` runs with NODE_ENV set to something
other than "development". That is not exotic: "test" is on the CLI's standard
list, `bin/next.ts` preserves an already-set value, and test runners and CI
commonly export it. The non-development branch then appends "/dev" to a path
that already ends in it, and `next dev` rewrites the user's tsconfig.json on
every start to add an inert include:
- include was updated to add '.next/dev/dev/types/**/*.ts'
With a configured `distDir` that itself ends in "dev" the same path produces
a third segment, e.g. `build/dev/dev/dev/types`.
`distDirRoot` is the configured value before the phase appends to it, and it
is already on the config. Both patterns follow from it directly -- `/types`
and `/dev/types` -- so the function no longer needs to know its phase and the
branch goes away. Callers pass `nextConfig.distDirRoot`; outside the
development phase it is equal to `distDir`, so nothing changes there.
Testing `endsWith('/dev')` on `distDir` instead would be wrong: it cannot
tell a suffix the phase added from one the user configured, and would strip a
`distDir: 'build/dev'` down to `build/types` during a build.
`getDevTypesPath` in the same file reads NODE_ENV too, but only to decide
whether to filter dev types out of a build, and its callers are not reached
on the `next dev` path. Left alone here.
Adds unit tests for the glob patterns, which had none.
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The root path is consistently propagated and the added tests cover the affected path-generation cases.
Review effort: Lite
Findings: None
What changed in this PR
Fixes phase/environment mismatches when generating TypeScript include globs by deriving both paths from distDirRoot.
Changes:
- Threads
distDirRootthrough all TypeScript setup callers. - Removes
NODE_ENV-based path selection. - Adds coverage for environment independence and custom
distDirvalues.
| File | Description |
|---|---|
type-paths.ts |
Derives stable type globs from the configured root. |
type-paths.test.ts |
Tests glob generation behavior. |
verify-typescript-setup.ts |
Accepts and forwards distDirRoot. |
writeConfigurationDefaults.ts |
Uses the root for tsconfig includes. |
setup-dev-bundler.ts |
Passes the root during development setup. |
next-typegen.ts |
Passes the root to type setup. |
next-test.ts |
Passes the root to type setup. |
build/type-check.ts |
Propagates the root through build type checking. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
getTypeDefinitionGlobPatternsemits both{distDir}/typesand{distDir}/dev/typesso switching between dev and build doesn't churntsconfig.json. Which one to derive by stripping/devand which by appending it was decided fromprocess.env.NODE_ENV.But
distDirgets its/devfrom the build phase —server/config.ts:The two signals disagree whenever
next devruns withNODE_ENVset to anything other thandevelopment. The non-development branch then appends/devto a path that already ends in it, andnext devrewrites the user'stsconfig.jsonon every start:.next/dev/devis never created, so the glob matches nothing — but the file is modified every run, and reverting it just brings it back next time.Why it happens silently
NODE_ENV=testis not exotic. It's on the CLI's own standard list (bin/next.ts),bin/next.tspreserves an already-set value rather than overriding it fordev, and Jest/Vitest set it while CI often exports it for the whole job. Becausetestis "standard", the non-standard-env warning never fires.With a configured
distDirthat itself ends indev, the same path yields a third segment —build/dev/dev/dev/types.The fix
distDirRootis the configureddistDirbefore the phase appends to it, and it's already on the config object. Both patterns follow from it directly, so the function no longer needs to know its phase and the branch goes away entirely:Callers pass
nextConfig.distDirRoot. Outside the development phase it equalsdistDir, so build,next typegenandnext testare unchanged.Why not
distDir.endsWith('/dev')— the obvious one-liner — it can't distinguish a suffix the phase added from one the user configured, and would stripdistDir: 'build/dev'down tobuild/typesduring a build. That's why this threads the root through instead of guessing from the path.Verification
I checked the new implementation against every combination of
distDirRoot× phase ×NODE_ENVI could construct — including customdistDirvalues ending indev— and it produces{root}/typesand{root}/dev/typesin all of them. The current code is correct only whereNODE_ENVhappens to agree with the phase.getTypeDefinitionGlobPatternshad no unit tests; this addstype-paths.test.tscovering the pattern pair, independence fromNODE_ENV, and thedistDir-ends-in-devcase.The existing
writeConfigurationDefaults.test.tsis unaffected: it passesdistDir: '.next', which is already root-shaped, so old and new agree.One thing I could not do: I don't have the monorepo built locally, so while I verified the pure-function logic exhaustively, the plumbing across the other five files is only verified by reading. Happy to adjust whatever CI flags.
Not included
getDevTypesPathin the same file also readsNODE_ENV, but only to decide whether to filter dev types out of a build, and its caller isn't reached on thenext devpath. It has the same latent mismatch and probably wants the same treatment, but I've left it out to keep this focused.Context
I originally reported this as #98939, which was auto-closed for not having a reproduction repo link. It reproduces on a stock
create-next-app:npx create-next-app@latest repro --ts --app cd repro NODE_ENV=test npx next devVerified on a clean scaffold with
next@16.3.5, resettingtsconfig.jsonfrom git before each run:NODE_ENVincludetest.next/dev/dev/types/**/*.tsdevelopmentUnset is fine because
bin/next.tsdefaults it todevelopmentfordev. Present in 16.3.4, 16.3.5 and 16.4.0-canary.36.