diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts index 14179ccecbe91..11c8d928ff5ad 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts @@ -595,6 +595,80 @@ describe('TargetProjectLocator', () => { expect(result).toEqual('child-pm-workspaces'); }); + + it('should convert relative file paths to absolute paths before TypeScript module resolution', () => { + const typescriptModule = require('nx/src/plugins/js/utils/typescript'); + const resolveModuleByImportSpy = jest + .spyOn(typescriptModule, 'resolveModuleByImport') + .mockReturnValue('/root/libs/proj/some-module.ts'); + + // Create a simple locator to test TypeScript resolution path + const simpleProjects: Record = { + proj: { + name: 'proj', + type: 'lib', + data: { root: 'libs/proj' }, + }, + }; + + const testLocator = new TargetProjectLocator( + simpleProjects, + {}, + new Map() + ); + + // Test with a relative path - the method should convert it to absolute + // We use a unique package name that won't be found via npm resolution + (testLocator as any).resolveImportWithTypescript( + 'package-that-is-installed-in-workspace-root', + 'libs/proj/index.ts' + ); + + // Verify that resolveModuleByImport was called with an absolute path + // We only care that the second parameter (filePath) was converted to absolute + expect(resolveModuleByImportSpy).toHaveBeenCalled(); + const [[importExpr, filePath]] = resolveModuleByImportSpy.mock.calls; + expect(importExpr).toBe('package-that-is-installed-in-workspace-root'); + expect(filePath).toBe('/root/libs/proj/index.ts'); // relative path should be converted to absolute + + resolveModuleByImportSpy.mockRestore(); + }); + + it('should keep absolute file paths as-is for TypeScript module resolution', () => { + const typescriptModule = require('nx/src/plugins/js/utils/typescript'); + const resolveModuleByImportSpy = jest + .spyOn(typescriptModule, 'resolveModuleByImport') + .mockReturnValue('/root/libs/proj/some-module.ts'); + + const simpleProjects: Record = { + proj: { + name: 'proj', + type: 'lib', + data: { root: 'libs/proj' }, + }, + }; + + const testLocator = new TargetProjectLocator( + simpleProjects, + {}, + new Map() + ); + + // Test with an absolute path - it should remain absolute + (testLocator as any).resolveImportWithTypescript( + 'package-that-is-installed-in-workspace-root', + '/root/libs/proj/index.ts' + ); + + // Verify that resolveModuleByImport was called with the same absolute path + // We only care that the second parameter (filePath) remained absolute + expect(resolveModuleByImportSpy).toHaveBeenCalled(); + const [[importExpr, filePath]] = resolveModuleByImportSpy.mock.calls; + expect(importExpr).toBe('package-that-is-installed-in-workspace-root'); + expect(filePath).toBe('/root/libs/proj/index.ts'); + + resolveModuleByImportSpy.mockRestore(); + }); }); describe('findTargetProjectWithImport (without tsconfig.json)', () => { diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts index 794f7764df270..1f59c44b6375e 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts @@ -1,5 +1,5 @@ import { isBuiltin } from 'node:module'; -import { dirname, join, posix, relative } from 'node:path'; +import { dirname, join, posix, relative, isAbsolute } from 'node:path'; import { clean, satisfies } from 'semver'; import type { ProjectGraphExternalNode, @@ -417,6 +417,11 @@ export class TargetProjectLocator { filePath: string ): string | undefined { let resolvedModule: string; + if (!isAbsolute(filePath)) { + // Convert to an absolute file path because TypeScript's module resolution won't + // properly walk up the directory tree (toward the workspace root) when given a relative path. + filePath = this.getAbsolutePath(filePath); + } const projectName = findProjectForPath(filePath, this.projectRootMappings); const cacheScope = projectName ? // fall back to the project name if the project root can't be determined