fix(plugin): rebase readonly import paths onto the output directory - #4019
Merged
kamilmysliwiec merged 1 commit intoAug 17, 2026
Merged
Conversation
In readonly mode the `from` side of a generated import kept the raw source path while the target side was rebased onto `outDir`, so the two were computed in different coordinate spaces. When `outDir` is deeper than `rootDir` the emitted relative import in the generated metadata leaked the output directory into the path and failed to resolve. Rebase the readonly `from` path through the same output-directory logic used for the non-readonly path, and let `ReadonlyVisitor` derive `outDir`/`rootDir` from the program's compiler options when the caller did not pass them explicitly (explicit options still take precedence), which is the case for the SWC metadata generator that constructs the visitor without those fields.
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
When the CLI plugin runs in readonly mode (the metadata-generation path used by the SWC builder), generated relative imports are computed incorrectly whenever
outDiris deeper thanrootDir: the output directory leaks into the emitted relative path and the import fails to resolve.For example, with
rootDir: "."andoutDir: "./dist", an import that should be../../entities/entity.dtois emitted as../../../dist/entities/entity.dto.Why
replaceImportPath()computes thefromside differently per mode:The non-readonly branch rebases
fromontooutDir(added in #3847), and the import target was later rebased as well (#3858). The readonly branch, however, keeps the rawpathToSourcewhile the target side still goes through the rebase, so the two are compared in different coordinate spaces.The second half of the problem is that even once the readonly branch rebases
from, no real caller suppliesoutDir/rootDirin readonly mode:nest-cli's metadata generator constructsReadonlyVisitorwithout them.Fix
frompath through the same output-directory logic as the non-readonly path (extracted into a shared helper reused bygetOutputDir).ReadonlyVisitor.visit()deriveoutDir/rootDirfromprogram.getCompilerOptions()when the caller did not pass them, mirroring howcompiler-plugin.ts'sbefore()hook already does it. Explicit options passed by a caller still take precedence.Tests
plugin-utils.spec.tscomparing readonly vs non-readonly output for a nestedoutDir, asserting no output-directory leak.readonly-visitor-outdir.spec.ts, modelled on the existingreadonly-visitor-project-refs.spec.ts: it builds a realts.Programfrom a fixture whose import target lives outsiderootDir(so the rebase is not accidentally cancelled out), constructsReadonlyVisitorwithout explicitoutDir/rootDir, and asserts the emitted import path is correct. Reverting either part of the fix independently makes this test fail. A second case asserts that an explicitoutDirpassed to the visitor is not overwritten by the program's compiler options.Full unit suite passes (406 tests).