Skip to content

Commit

Permalink
fix(core): improve resolution of packages in package manager workspac…
Browse files Browse the repository at this point in the history
…es when constructing the project graph
  • Loading branch information
leosvelperez committed Jan 29, 2025
1 parent 8266785 commit 5cc03ea
Show file tree
Hide file tree
Showing 14 changed files with 560 additions and 104 deletions.
4 changes: 2 additions & 2 deletions docs/generated/devkit/ProjectGraphProjectNode.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A node describing a project in a workspace

- [data](../../devkit/documents/ProjectGraphProjectNode#data): ProjectConfiguration & Object
- [name](../../devkit/documents/ProjectGraphProjectNode#name): string
- [type](../../devkit/documents/ProjectGraphProjectNode#type): "lib" | "app" | "e2e"
- [type](../../devkit/documents/ProjectGraphProjectNode#type): "app" | "e2e" | "lib"

## Properties

Expand All @@ -28,4 +28,4 @@ Additional metadata about a project

### type

**type**: `"lib"` \| `"app"` \| `"e2e"`
**type**: `"app"` \| `"e2e"` \| `"lib"`
18 changes: 16 additions & 2 deletions packages/nx/plugins/package-json.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { createNodesFromFiles, NxPluginV2 } from '../src/project-graph/plugins';
import { workspaceRoot } from '../src/utils/workspace-root';
import { createNodeFromPackageJson } from '../src/plugins/package-json';
import {
buildPackageJsonWorkspacesMatcher,
createNodeFromPackageJson,
} from '../src/plugins/package-json';
import { workspaceDataDirectory } from '../src/utils/cache-directory';
import { join } from 'path';
import { ProjectConfiguration } from '../src/config/workspace-json-project-json';
Expand Down Expand Up @@ -31,8 +34,19 @@ const plugin: NxPluginV2 = {
(configFiles, options, context) => {
const cache = readPackageJsonConfigurationCache();

const isInPackageJsonWorkspaces = buildPackageJsonWorkspacesMatcher(
context.workspaceRoot,
(f) => readJsonFile(join(context.workspaceRoot, f))
);

const result = createNodesFromFiles(
(f) => createNodeFromPackageJson(f, workspaceRoot, cache),
(packageJsonPath) =>
createNodeFromPackageJson(
packageJsonPath,
workspaceRoot,
cache,
isInPackageJsonWorkspaces(packageJsonPath)
),
configFiles,
options,
context
Expand Down
10 changes: 5 additions & 5 deletions packages/nx/src/config/schema-utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { existsSync } from 'fs';
import { extname, join } from 'path';
import { resolve as resolveExports } from 'resolve.exports';
import { getPackageEntryPointsToProjectMap } from '../plugins/js/utils/packages';
import { getWorkspacePackagesMetadata } from '../plugins/js/utils/packages';
import { registerPluginTSTranspiler } from '../project-graph/plugins';
import { normalizePath } from '../utils/path';
import type { ProjectConfiguration } from './workspace-json-project-json';
Expand Down Expand Up @@ -119,16 +119,16 @@ export function resolveSchema(
});
}

let packageEntryPointsToProjectMap: Record<string, ProjectConfiguration>;
let packageToProjectMap: Record<string, ProjectConfiguration>;
function tryResolveFromSource(
path: string,
directory: string,
packageName: string,
projects: Record<string, ProjectConfiguration>
): string | null {
packageEntryPointsToProjectMap ??=
getPackageEntryPointsToProjectMap(projects);
const localProject = packageEntryPointsToProjectMap[packageName];
packageToProjectMap ??=
getWorkspacePackagesMetadata(projects).packageToProjectMap;
const localProject = packageToProjectMap[packageName];
if (!localProject) {
// it doesn't match any of the package names from the local projects
return null;
Expand Down
1 change: 1 addition & 0 deletions packages/nx/src/config/to-project-name.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('Workspaces', () => {
"metadata": {
"description": "my-package description",
"js": {
"isInPackageManagerWorkspaces": true,
"packageName": "my-package",
},
"targetGroups": {},
Expand Down
2 changes: 2 additions & 0 deletions packages/nx/src/config/workspace-json-project-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export interface ProjectMetadata {
js?: {
packageName: string;
packageExports?: PackageJson['exports'];
packageMain?: string;
isInPackageManagerWorkspaces?: boolean;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ describe('explicit package json dependencies', () => {
data: {
root: 'libs/proj',
metadata: {
js: { packageName: 'proj', packageExports: undefined },
js: {
packageName: 'proj',
packageExports: undefined,
isInPackageManagerWorkspaces: true,
},
},
},
},
Expand All @@ -84,7 +88,11 @@ describe('explicit package json dependencies', () => {
data: {
root: 'libs/proj2',
metadata: {
js: { packageName: 'proj2', packageExports: undefined },
js: {
packageName: 'proj2',
packageExports: undefined,
isInPackageManagerWorkspaces: true,
},
},
},
},
Expand All @@ -94,7 +102,11 @@ describe('explicit package json dependencies', () => {
data: {
root: 'libs/proj4',
metadata: {
js: { packageName: 'proj3', packageExports: undefined },
js: {
packageName: 'proj3',
packageExports: undefined,
isInPackageManagerWorkspaces: true,
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ describe('TargetProjectLocator', () => {
js: {
packageName: '@proj/child-pm-workspaces',
packageExports: undefined,
isInPackageManagerWorkspaces: true,
packageMain: 'index.ts',
},
},
},
Expand Down Expand Up @@ -1011,6 +1013,197 @@ describe('TargetProjectLocator', () => {
expect(result).toEqual('npm:[email protected]');
});
});

describe('findDependencyInWorkspaceProjects', () => {
it.each`
exports
${undefined}
${'dist/index.js'}
${{}}
${{ '.': 'dist/index.js' }}
${{ './subpath': './dist/subpath.js' }}
${{ import: './dist/index.js', default: './dist/index.js' }}
`(
'should find "@org/pkg1" package as "pkg1" project when exports="$exports"',
({ exports }) => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
packageExports: exports,
isInPackageManagerWorkspaces: true,
},
},
},
},
};

const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findDependencyInWorkspaceProjects('@org/pkg1');

expect(result).toEqual('pkg1');
}
);

it('should not match "@org/pkg2" when there is no workspace project with that package name', () => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
isInPackageManagerWorkspaces: true,
},
},
},
},
};

const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findDependencyInWorkspaceProjects('@org/pkg2');

expect(result).toBeFalsy();
});
});

describe('findImportInWorkspaceProjects', () => {
it.each`
exports | importPath
${'dist/index.js'} | ${'@org/pkg1'}
${{ '.': 'dist/index.js' }} | ${'@org/pkg1'}
${{ './subpath': './dist/subpath.js' }} | ${'@org/pkg1/subpath'}
${{ './*': './dist/*.js' }} | ${'@org/pkg1/subpath'}
${{ import: './dist/index.js', default: './dist/index.js' }} | ${'@org/pkg1'}
`(
'should find "$importPath" as "pkg1" project when exports="$exports"',
({ exports, importPath }) => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
packageExports: exports,
isInPackageManagerWorkspaces: true,
},
},
},
},
};

const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findImportInWorkspaceProjects(importPath);

expect(result).toEqual('pkg1');
}
);

it.each`
exports | importPath
${'dist/index.js'} | ${'@org/pkg1'}
${{ '.': 'dist/index.js' }} | ${'@org/pkg1'}
${{ './subpath': './dist/subpath.js' }} | ${'@org/pkg1/subpath'}
${{ './*': './dist/*.js' }} | ${'@org/pkg1/subpath'}
${{ import: './dist/index.js', default: './dist/index.js' }} | ${'@org/pkg1'}
`(
'should not find "$importPath" as "pkg1" project when exports="$exports" and isInPackageManagerWorkspaces is false',
({ exports, importPath }) => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
packageExports: exports,
isInPackageManagerWorkspaces: false,
},
},
},
},
};

const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findImportInWorkspaceProjects(importPath);

expect(result).toBeFalsy();
}
);

it.each`
exports | importPath
${undefined} | ${'@org/pkg1'}
${{}} | ${'@org/pkg1'}
${{ '.': 'dist/index.js' }} | ${'@org/pkg1/subpath'}
${{ './subpath': './dist/subpath.js' }} | ${'@org/pkg1/subpath/extra-path'}
${{ './*': './dist/*.js' }} | ${'@org/pkg1/subpath/extra-path'}
${{ './feature': null }} | ${'@org/pkg1/feature'}
${{ import: './dist/index.js', default: './dist/index.js' }} | ${'@org/pkg1/subpath'}
`(
'should not match "$importPath" when exports="$exports"',
({ exports, importPath }) => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
packageExports: exports,
isInPackageManagerWorkspaces: true,
},
},
},
},
};

const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findImportInWorkspaceProjects(importPath);

expect(result).toBeFalsy();
}
);
});
});

describe('isBuiltinModuleImport()', () => {
Expand Down
Loading

0 comments on commit 5cc03ea

Please sign in to comment.