Skip to content

Commit 3735641

Browse files
authored
fix(core): convert filePath to an absolute path before typescript resolves the module (#34001)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> TypeScript’s module resolution stop at project's root when resolving modules. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> TypeScript’s module resolution will walk up to the workspace root when resolving modules ## Changes Made Convert the filePath to an absolute path inside findProjectFromImport before calling resolveImportWithTypescript, because TypeScript’s module resolution will not correctly traverse up the directory tree toward the workspace root when given a relative path. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #33985
1 parent 768c580 commit 3735641

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,80 @@ describe('TargetProjectLocator', () => {
595595

596596
expect(result).toEqual('child-pm-workspaces');
597597
});
598+
599+
it('should convert relative file paths to absolute paths before TypeScript module resolution', () => {
600+
const typescriptModule = require('nx/src/plugins/js/utils/typescript');
601+
const resolveModuleByImportSpy = jest
602+
.spyOn(typescriptModule, 'resolveModuleByImport')
603+
.mockReturnValue('/root/libs/proj/some-module.ts');
604+
605+
// Create a simple locator to test TypeScript resolution path
606+
const simpleProjects: Record<string, ProjectGraphProjectNode> = {
607+
proj: {
608+
name: 'proj',
609+
type: 'lib',
610+
data: { root: 'libs/proj' },
611+
},
612+
};
613+
614+
const testLocator = new TargetProjectLocator(
615+
simpleProjects,
616+
{},
617+
new Map()
618+
);
619+
620+
// Test with a relative path - the method should convert it to absolute
621+
// We use a unique package name that won't be found via npm resolution
622+
(testLocator as any).resolveImportWithTypescript(
623+
'package-that-is-installed-in-workspace-root',
624+
'libs/proj/index.ts'
625+
);
626+
627+
// Verify that resolveModuleByImport was called with an absolute path
628+
// We only care that the second parameter (filePath) was converted to absolute
629+
expect(resolveModuleByImportSpy).toHaveBeenCalled();
630+
const [[importExpr, filePath]] = resolveModuleByImportSpy.mock.calls;
631+
expect(importExpr).toBe('package-that-is-installed-in-workspace-root');
632+
expect(filePath).toBe('/root/libs/proj/index.ts'); // relative path should be converted to absolute
633+
634+
resolveModuleByImportSpy.mockRestore();
635+
});
636+
637+
it('should keep absolute file paths as-is for TypeScript module resolution', () => {
638+
const typescriptModule = require('nx/src/plugins/js/utils/typescript');
639+
const resolveModuleByImportSpy = jest
640+
.spyOn(typescriptModule, 'resolveModuleByImport')
641+
.mockReturnValue('/root/libs/proj/some-module.ts');
642+
643+
const simpleProjects: Record<string, ProjectGraphProjectNode> = {
644+
proj: {
645+
name: 'proj',
646+
type: 'lib',
647+
data: { root: 'libs/proj' },
648+
},
649+
};
650+
651+
const testLocator = new TargetProjectLocator(
652+
simpleProjects,
653+
{},
654+
new Map()
655+
);
656+
657+
// Test with an absolute path - it should remain absolute
658+
(testLocator as any).resolveImportWithTypescript(
659+
'package-that-is-installed-in-workspace-root',
660+
'/root/libs/proj/index.ts'
661+
);
662+
663+
// Verify that resolveModuleByImport was called with the same absolute path
664+
// We only care that the second parameter (filePath) remained absolute
665+
expect(resolveModuleByImportSpy).toHaveBeenCalled();
666+
const [[importExpr, filePath]] = resolveModuleByImportSpy.mock.calls;
667+
expect(importExpr).toBe('package-that-is-installed-in-workspace-root');
668+
expect(filePath).toBe('/root/libs/proj/index.ts');
669+
670+
resolveModuleByImportSpy.mockRestore();
671+
});
598672
});
599673

600674
describe('findTargetProjectWithImport (without tsconfig.json)', () => {

packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { isBuiltin } from 'node:module';
2-
import { dirname, join, posix, relative } from 'node:path';
2+
import { dirname, join, posix, relative, isAbsolute } from 'node:path';
33
import { clean, satisfies } from 'semver';
44
import type {
55
ProjectGraphExternalNode,
@@ -417,6 +417,11 @@ export class TargetProjectLocator {
417417
filePath: string
418418
): string | undefined {
419419
let resolvedModule: string;
420+
if (!isAbsolute(filePath)) {
421+
// Convert to an absolute file path because TypeScript's module resolution won't
422+
// properly walk up the directory tree (toward the workspace root) when given a relative path.
423+
filePath = this.getAbsolutePath(filePath);
424+
}
420425
const projectName = findProjectForPath(filePath, this.projectRootMappings);
421426
const cacheScope = projectName
422427
? // fall back to the project name if the project root can't be determined

0 commit comments

Comments
 (0)