fix(js): infer outputs correctly when both rootDir and outDir are set for tsconfig (#29531)

When the tsconfig has `rootDir` and `outDir` both defined, then the
`*.tsbuildinfo` file is not cached. This makes incremental tsc not work
through distribution (e.g. agents).

For example, given this `tsconfig.lib.json` file:

```json
{
  "compilerOptions": {
    "outDir": "out-tsc/lib-1",
    "rootDir": "src"
  }
}
```

The outputs (e.g. `*.d.ts` files) are under
`{projectRoot}/out-tsc/lib-1`, but the tsbuild info file is under
`{projectRoot}/out-tsc/tsconfig.lib.tsbuildinfo`.

## Current Behavior
tsbuildinfo file is not cached

## Expected Behavior
tsbuildinfo file is cached

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
This commit is contained in:
Jack Hsu 2025-01-07 11:17:23 -05:00 committed by GitHub
parent 82751a1446
commit df27a97c7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 140 additions and 8 deletions

View File

@ -1194,6 +1194,118 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
`);
});
it('should include tsbuildinfo file when outDir and rootDir at both set', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
files: [],
references: [{ path: './tsconfig.lib.json' }],
}),
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
compilerOptions: { outDir: 'out-tsc/my-lib', rootDir: 'src' },
files: ['src/main.ts'],
}),
'libs/my-lib/package.json': `{}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
build: {
configName: 'tsconfig.lib.json',
},
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"dependsOn": [
"^build",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/out-tsc/my-lib",
"{projectRoot}/out-tsc/*.tsbuildinfo",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"dependsOn": [
"^typecheck",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/out-tsc/my-lib",
"{projectRoot}/out-tsc/*.tsbuildinfo",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should add the inline output files when `outDir` is not defined', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
@ -2771,6 +2883,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
},
"outputs": [
"{workspaceRoot}/dist/libs/my-lib",
"{workspaceRoot}/dist/libs/*.tsbuildinfo",
],
"syncGenerators": [
"@nx/js:typescript-sync",
@ -2933,6 +3046,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"{workspaceRoot}/dist/libs/my-lib/lib.d.ts.map",
"{workspaceRoot}/dist/libs/my-lib/lib.tsbuildinfo",
"{workspaceRoot}/dist/libs/my-lib/other",
"{workspaceRoot}/dist/libs/my-lib/*.tsbuildinfo",
],
"syncGenerators": [
"@nx/js:typescript-sync",

View File

@ -549,16 +549,34 @@ function getOutputs(
pathToInputOrOutput(config.options.outDir, workspaceRoot, projectRoot)
);
if (
config.options.tsBuildInfoFile &&
!normalize(config.options.tsBuildInfoFile).startsWith(
`${normalize(config.options.outDir)}${sep}`
)
) {
// https://www.typescriptlang.org/tsconfig#tsBuildInfoFile
if (config.options.tsBuildInfoFile) {
if (
!normalize(config.options.tsBuildInfoFile).startsWith(
`${normalize(config.options.outDir)}${sep}`
)
) {
// https://www.typescriptlang.org/tsconfig#tsBuildInfoFile
outputs.add(
pathToInputOrOutput(
config.options.tsBuildInfoFile,
workspaceRoot,
projectRoot
)
);
}
} else if (config.options.rootDir && config.options.rootDir !== '.') {
// If rootDir is set, then the tsbuildinfo file will be outside the outDir so we need to add it.
const relativeRootDir = relative(
config.options.rootDir,
join(workspaceRoot, projectRoot)
);
outputs.add(
pathToInputOrOutput(
config.options.tsBuildInfoFile,
joinPathFragments(
config.options.outDir,
relativeRootDir,
`*.tsbuildinfo`
),
workspaceRoot,
projectRoot
)