From df27a97c7e9c18fe3b39a70bbff071c11c02b724 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Tue, 7 Jan 2025 11:17:23 -0500 Subject: [PATCH] fix(js): infer outputs correctly when both rootDir and outDir are set for tsconfig (#29531) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Fixes # --------- Co-authored-by: Leosvel Pérez Espinosa --- .../js/src/plugins/typescript/plugin.spec.ts | 114 ++++++++++++++++++ packages/js/src/plugins/typescript/plugin.ts | 34 ++++-- 2 files changed, 140 insertions(+), 8 deletions(-) diff --git a/packages/js/src/plugins/typescript/plugin.spec.ts b/packages/js/src/plugins/typescript/plugin.spec.ts index 4f6292092a..47dd7b9c74 100644 --- a/packages/js/src/plugins/typescript/plugin.spec.ts +++ b/packages/js/src/plugins/typescript/plugin.spec.ts @@ -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", diff --git a/packages/js/src/plugins/typescript/plugin.ts b/packages/js/src/plugins/typescript/plugin.ts index a92eb66752..630dd17539 100644 --- a/packages/js/src/plugins/typescript/plugin.ts +++ b/packages/js/src/plugins/typescript/plugin.ts @@ -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 )