@nx/esbuild:esbuild resolves tsConfig relative to current working directory
Current Behavior
@nx/esbuild:esbuild resolves the tsConfig option relative to the current working directory when it initially reads the TypeScript configuration during option normalization.
This causes the same Nx build to behave differently depending on the directory from which nx is invoked.
Expected Behavior
The tsConfig path used by @nx/esbuild:esbuild should be resolved consistently relative to the Nx workspace root, regardless of the current working directory.
GitHub Repo
No response
Steps to Reproduce
Create a fresh Nx workspace:
PS D:\code> npx [email protected] demo --appName demo --preset=node-monorepo --framework=none --docker false --formatter=none --linter=none --unitTestRunner=none --analytics false --nxCloud skip
NX Let's create a new workspace [https://nx.dev/getting-started/intro]
NX Creating your v23.2.1 workspace.
✔ Installing dependencies with npm
✔ Successfully created the workspace: demoRun the build from the workspace root:
PS D:\code> cd demo
PS D:\code\demo> npx nx build demo --skipNxCache
> nx run @demo/demo:build:production
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
Run duration: 2.1s
Cache: Skipped (--skip-nx-cache)
Critical path: 1.9s (1 task)
Recoverable time: <1ms
PS D:\code\demo>The same build fails when run from the project's directory:
PS D:\code\demo> cd apps/demo
PS D:\code\demo\apps\demo> npx nx build demo --skipNxCache
> nx run @demo/demo:build:production
error TS5069: Option 'declarationMap' cannot be specified without specifying option 'declaration' or option 'composite'.
error TS5069: Option 'emitDeclarationOnly' cannot be specified without specifying option 'declaration' or option 'composite'.
Found 2 errors.
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Ran target build for project @demo/demo (2s)
× 1/1 failed
√ 0/1 succeeded [0 read from cache]
NX Nx detected a flaky task
@demo/demo:build:production
Flaky tasks can disrupt your CI pipeline. Automatically retry them with Nx Cloud. Learn more at https://nx.dev/ci/features/flaky-tasks
Run duration: 1.7s
Cache: Skipped (--skip-nx-cache)
Critical path: 1.6s (1 task)
Recoverable time: <1ms
PS D:\code\demo\apps\demo>Nx Report
Node : 24.18.0
OS : win32-x64
Native Target : x86_64-windows
npm : 11.16.0
daemon : Available
nx : 23.2.1
@nx/js : 23.2.1
@nx/eslint : 23.2.1
@nx/workspace : 23.2.1
@nx/jest : 23.2.1
@nx/devkit : 23.2.1
@nx/esbuild : 23.2.1
@nx/node : 23.2.1
@nx/docker : 23.2.1
typescript : 6.0.3
---------------------------------------
Registered Plugins:
@nx/js/typescript
---------------------------------------
Cache
- Usage: 0.00 B / 23.10 GB (0.0%)
- Location: ~\.nx\42b10a8fd28ebbad\cacheFailure Logs
error TS5069: Option 'declarationMap' cannot be specified without specifying option 'declaration' or option 'composite'.
error TS5069: Option 'emitDeclarationOnly' cannot be specified without specifying option 'declaration' or option 'composite'.
Found 2 errors.Package Manager Version
npm 11.16.0
Operating System
- macOS
- Linux
- Windows
- Other (Please specify)
Additional Information
The build target reports its tsConfig as a workspace-relative path. The following test obtains that path directly from nx show project and passes it to readTsConfig().
From the workspace root:
PS D:\code\demo> node -e "const { execSync } = require('node:child_process'); const { readTsConfig } = require('@nx/js'); const project = JSON.parse(execSync('npx nx show project demo --json', { encoding: 'utf8' })); const tsConfig = project.targets.build.options.tsConfig; const c = readTsConfig(tsConfig); console.log({ cwd: process.cwd(), tsConfig, options: c.options, errors: c.errors });"
{
cwd: 'D:\\code\\demo',
tsConfig: 'apps/demo/tsconfig.app.json',
options: {
composite: true,
declarationMap: true,
emitDeclarationOnly: true,
importHelpers: true,
isolatedModules: true,
lib: [ 'lib.es2022.d.ts' ],
module: 199,
moduleResolution: 99,
noEmitOnError: true,
noFallthroughCasesInSwitch: true,
noImplicitOverride: true,
noImplicitReturns: true,
noUnusedLocals: true,
skipLibCheck: true,
strict: true,
target: 9,
customConditions: [ '@demo/source' ],
outDir: 'D:/code/demo/apps/demo/dist',
types: [ 'node' ],
rootDir: 'D:/code/demo/apps/demo/src',
tsBuildInfoFile: 'D:/code/demo/apps/demo/dist/tsconfig.app.tsbuildinfo',
configFilePath: undefined
},
errors: []
}
PS D:\code\demo>From the project directory:
PS D:\code\demo> cd apps/demo
PS D:\code\demo\apps\demo> node -e "const { execSync } = require('node:child_process'); const { readTsConfig } = require('@nx/js'); const project = JSON.parse(execSync('npx nx show project demo --json', { encoding: 'utf8' })); const tsConfig = project.targets.build.options.tsConfig; const c = readTsConfig(tsConfig); console.log({ cwd: process.cwd(), tsConfig, options: c.options, errors: c.errors });"
{
cwd: 'D:\\code\\demo\\apps\\demo',
tsConfig: 'apps/demo/tsconfig.app.json',
options: { configFilePath: undefined },
errors: [
{
file: undefined,
start: undefined,
length: undefined,
messageText: `No inputs were found in config file 'tsconfig.json'. Specified 'include' paths were '["**/*"]' and 'exclude' paths were '[]'.`,
category: 1,
code: 18003,
reportsUnnecessary: undefined,
reportsDeprecated: undefined
}
]
}
PS D:\code\demo\apps\demo>This demonstrates that the same tsConfig value returned by Nx is interpreted differently by readTsConfig() depending on the current working directory.
The relevant call in @nx/esbuild 23.2.1 is:
const tsConfig = readTsConfig(options.tsConfig);in packages/esbuild/src/executors/esbuild/lib/normalize.ts#L25.
The readTsConfig() implementation is in packages/js/src/utils/typescript/ts-config.ts#L15.
Nx 23.2.0 also includes a related fix for esbuild paths being resolved from the workspace root instead of the cwd: PR #36830
Source: nrwl/nx