diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts index 529caa2b39ce5..d9a57dd5f14f6 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts @@ -73,17 +73,30 @@ describe('explicit package json dependencies', () => { type: 'lib', data: { root: 'libs/proj', + metadata: { + js: { packageName: 'proj', packageExports: undefined }, + }, }, }, proj2: { name: 'proj2', type: 'lib', - data: { root: 'libs/proj2' }, + data: { + root: 'libs/proj2', + metadata: { + js: { packageName: 'proj2', packageExports: undefined }, + }, + }, }, proj3: { name: 'proj3', type: 'lib', - data: { root: 'libs/proj4' }, + data: { + root: 'libs/proj4', + metadata: { + js: { packageName: 'proj3', packageExports: undefined }, + }, + }, }, }; @@ -130,7 +143,7 @@ describe('explicit package json dependencies', () => { it(`should add dependencies with mixed versions for projects based on deps in package.json and populate the cache`, async () => { const npmResolutionCache = new Map(); const targetProjectLocator = new TargetProjectLocator( - {}, + projects, ctx.externalNodes, npmResolutionCache ); @@ -173,7 +186,7 @@ describe('explicit package json dependencies', () => { it(`should preferentially resolve external projects found in the npmResolutionCache`, async () => { const npmResolutionCache = new Map(); const targetProjectLocator = new TargetProjectLocator( - {}, + projects, ctx.externalNodes, npmResolutionCache ); diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts index a27d8c2c3977c..5d5bfd040c8b7 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts @@ -1,17 +1,13 @@ -import { dirname, join } from 'node:path'; import { DependencyType } from '../../../../config/project-graph'; -import { - ProjectConfiguration, - ProjectsConfigurations, -} from '../../../../config/workspace-json-project-json'; +import type { ProjectConfiguration } from '../../../../config/workspace-json-project-json'; import { defaultFileRead } from '../../../../project-graph/file-utils'; -import { CreateDependenciesContext } from '../../../../project-graph/plugins'; +import type { CreateDependenciesContext } from '../../../../project-graph/plugins'; import { - RawProjectGraphDependency, + type RawProjectGraphDependency, validateDependency, } from '../../../../project-graph/project-graph-builder'; import { parseJson } from '../../../../utils/json'; -import { PackageJson } from '../../../../utils/package-json'; +import type { PackageJson } from '../../../../utils/package-json'; import { joinPathFragments } from '../../../../utils/path'; import { TargetProjectLocator } from './target-project-locator'; @@ -20,40 +16,17 @@ export function buildExplicitPackageJsonDependencies( targetProjectLocator: TargetProjectLocator ): RawProjectGraphDependency[] { const res: RawProjectGraphDependency[] = []; - let packageNameMap = undefined; const nodes = Object.values(ctx.projects); Object.keys(ctx.filesToProcess.projectFileMap).forEach((source) => { Object.values(ctx.filesToProcess.projectFileMap[source]).forEach((f) => { if (isPackageJsonAtProjectRoot(nodes, f.file)) { - // we only create the package name map once and only if a package.json file changes - packageNameMap = packageNameMap || createPackageNameMap(ctx.projects); - processPackageJson( - source, - f.file, - ctx, - targetProjectLocator, - res, - packageNameMap - ); + processPackageJson(source, f.file, ctx, targetProjectLocator, res); } }); }); return res; } -function createPackageNameMap(projects: ProjectsConfigurations['projects']) { - const res = {}; - for (let projectName of Object.keys(projects)) { - try { - const packageJson = parseJson( - defaultFileRead(join(projects[projectName].root, 'package.json')) - ); - res[packageJson.name ?? projectName] = projectName; - } catch (e) {} - } - return res; -} - function isPackageJsonAtProjectRoot( nodes: ProjectConfiguration[], fileName: string @@ -72,18 +45,19 @@ function processPackageJson( fileName: string, ctx: CreateDependenciesContext, targetProjectLocator: TargetProjectLocator, - collectedDeps: RawProjectGraphDependency[], - packageNameMap: { [packageName: string]: string } + collectedDeps: RawProjectGraphDependency[] ) { try { const deps = readDeps(parseJson(defaultFileRead(fileName))); for (const d of Object.keys(deps)) { - // package.json refers to another project in the monorepo - if (packageNameMap[d]) { + const localProject = + targetProjectLocator.findDependencyInWorkspaceProjects(d); + if (localProject) { + // package.json refers to another project in the monorepo const dependency: RawProjectGraphDependency = { source: sourceProject, - target: packageNameMap[d], + target: localProject, sourceFile: fileName, type: DependencyType.static, }; 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 19b80435baa29..a740e3c3263c7 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 @@ -207,6 +207,24 @@ describe('TargetProjectLocator', () => { root: 'libs/parent-path/child-path', }, }, + 'parent-pm-workspaces': { + name: 'parent-pm-workspaces', + type: 'lib', + data: { root: 'packages/parent-pm-workspaces' }, + }, + 'child-pm-workspaces': { + name: 'child-pm-workspaces', + type: 'lib', + data: { + root: 'packages/child-pm-workspaces', + metadata: { + js: { + packageName: '@proj/child-pm-workspaces', + packageExports: undefined, + }, + }, + }, + }, }; npmProjects = { 'npm:@ng/core': { @@ -494,6 +512,19 @@ describe('TargetProjectLocator', () => { ); expect(lodash4).toEqual('npm:lodash-4'); }); + + it('should resolve local packages linked using package manager workspaces', () => { + const targetProjectLocator = new TargetProjectLocator( + projects, + npmProjects + ); + const result = targetProjectLocator.findProjectFromImport( + '@proj/child-pm-workspaces', + 'packages/parent-pm-workspaces/index.ts' + ); + + expect(result).toEqual('child-pm-workspaces'); + }); }); 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 4f6367ace7bdc..e4752cd89852c 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,7 +1,7 @@ import { isBuiltin } from 'node:module'; import { dirname, join, posix, relative } from 'node:path'; import { clean } from 'semver'; -import { +import type { ProjectGraphExternalNode, ProjectGraphProjectNode, } from '../../../../config/project-graph'; @@ -10,14 +10,15 @@ import { findProjectForPath, } from '../../../../project-graph/utils/find-project-for-path'; import { isRelativePath, readJsonFile } from '../../../../utils/fileutils'; -import { PackageJson } from '../../../../utils/package-json'; +import { getPackageNameFromImportPath } from '../../../../utils/get-package-name-from-import-path'; +import type { PackageJson } from '../../../../utils/package-json'; import { workspaceRoot } from '../../../../utils/workspace-root'; +import { getPackageEntryPointsToProjectMap } from '../../utils/packages'; import { resolveRelativeToDir } from '../../utils/resolve-relative-to-dir'; import { getRootTsConfigFileName, resolveModuleByImport, } from '../../utils/typescript'; -import { getPackageNameFromImportPath } from '../../../../utils/get-package-name-from-import-path'; /** * The key is a combination of the package name and the workspace relative directory @@ -44,6 +45,10 @@ export class TargetProjectLocator { private tsConfig = this.getRootTsConfig(); private paths = this.tsConfig.config?.compilerOptions?.paths; private typescriptResolutionCache = new Map(); + private packageEntryPointsToProjectMap: Record< + string, + ProjectGraphProjectNode + >; constructor( private readonly nodes: Record, @@ -135,6 +140,13 @@ export class TargetProjectLocator { return this.findProjectOfResolvedModule(resolvedModule); } catch {} + // fall back to see if it's a locally linked workspace project where the + // output might not exist yet + const localProject = this.findDependencyInWorkspaceProjects(importExpr); + if (localProject) { + return localProject; + } + // nothing found, cache for later this.npmResolutionCache.set(importExpr, null); return null; @@ -242,6 +254,14 @@ export class TargetProjectLocator { return undefined; } + findDependencyInWorkspaceProjects(dep: string): string | null { + this.packageEntryPointsToProjectMap ??= getPackageEntryPointsToProjectMap( + this.nodes + ); + + return this.packageEntryPointsToProjectMap[dep]?.name ?? null; + } + private resolveImportWithTypescript( normalizedImportExpr: string, filePath: string diff --git a/packages/nx/src/plugins/js/utils/packages.ts b/packages/nx/src/plugins/js/utils/packages.ts index a39c794fe6b60..0625deedec688 100644 --- a/packages/nx/src/plugins/js/utils/packages.ts +++ b/packages/nx/src/plugins/js/utils/packages.ts @@ -1,16 +1,20 @@ import { join } from 'node:path/posix'; +import type { ProjectGraphProjectNode } from '../../../config/project-graph'; import type { ProjectConfiguration } from '../../../config/workspace-json-project-json'; -export function getPackageEntryPointsToProjectMap( - projects: Record -): Record { - const result: Record = {}; +export function getPackageEntryPointsToProjectMap< + T extends ProjectGraphProjectNode | ProjectConfiguration +>(projects: Record): Record { + const result: Record = {}; for (const project of Object.values(projects)) { - if (!project.metadata?.js) { + const metadata = + 'data' in project ? project.data.metadata : project.metadata; + + if (!metadata?.js) { continue; } - const { packageName, packageExports } = project.metadata.js; + const { packageName, packageExports } = metadata.js; if (!packageExports || typeof packageExports === 'string') { // no `exports` or it points to a file, which would be the equivalent of // an '.' export, in which case the package name is the entry point